diff --git a/.vscode/settings.json b/.vscode/settings.json index d6e744b..36fbd98 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,8 @@ "ficonsax", "jellyfin", "Jellyfin", - "LTRB" + "LTRB", + "LTWH" ], "dart.flutterSdkPath": ".fvm/versions/3.24.3", "search.exclude": { diff --git a/lib/models/items/trick_play_model.dart b/lib/models/items/trick_play_model.dart index 1f0b03d..1aaeddc 100644 --- a/lib/models/items/trick_play_model.dart +++ b/lib/models/items/trick_play_model.dart @@ -22,8 +22,8 @@ class TrickPlayModel with _$TrickPlayModel { int get imagesPerTile => tileWidth * tileHeight; String? getTile(Duration position) { - final int currentIndex = (position.inMilliseconds ~/ interval.inMilliseconds).clamp(0, thumbnailCount); - final int indexOfTile = (currentIndex ~/ imagesPerTile).clamp(0, images.length); + final int currentIndex = (position.inMilliseconds ~/ interval.inMilliseconds).clamp(0, thumbnailCount - 1); + final int indexOfTile = (currentIndex ~/ imagesPerTile).clamp(0, (images.length - 1)); return images.elementAtOrNull(indexOfTile); } @@ -32,7 +32,10 @@ class TrickPlayModel with _$TrickPlayModel { final int tileIndex = currentIndex % imagesPerTile; final int column = tileIndex % tileWidth; final int row = tileIndex ~/ tileWidth; - return Offset((width * column).toDouble(), (height * row).toDouble()); + return Offset( + (width * column).toDouble(), + (height * row).toDouble(), + ); } static Map toTrickPlayMap(Map map) { diff --git a/lib/models/playback/direct_playback_model.dart b/lib/models/playback/direct_playback_model.dart index 03e1be7..5729814 100644 --- a/lib/models/playback/direct_playback_model.dart +++ b/lib/models/playback/direct_playback_model.dart @@ -1,9 +1,4 @@ import 'package:collection/collection.dart'; -import 'package:fladder/models/items/trick_play_model.dart'; -import 'package:fladder/util/list_extensions.dart'; -import 'package:fladder/wrappers/media_control_wrapper.dart' - if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:media_kit/media_kit.dart'; @@ -12,10 +7,15 @@ import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/intro_skip_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; +import 'package:fladder/models/items/trick_play_model.dart'; import 'package:fladder/models/playback/playback_model.dart'; import 'package:fladder/providers/api_provider.dart'; import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/util/duration_extensions.dart'; +import 'package:fladder/util/list_extensions.dart'; +import 'package:fladder/wrappers/media_control_wrapper.dart' + if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart'; +import 'package:flutter/widgets.dart'; class DirectPlaybackModel implements PlaybackModel { DirectPlaybackModel({ @@ -147,13 +147,6 @@ class DirectPlaybackModel implements PlaybackModel { @override Future updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async { final api = ref.read(jellyApiProvider); - - //Check for newly generated scrubImages - if (trickPlay == null) { - final trickplay = await api.getTrickPlay(item: item, ref: ref); - ref.read(playBackModel.notifier).update((state) => copyWith(trickPlay: () => trickplay?.body)); - } - await api.sessionsPlayingProgressPost( body: PlaybackProgressInfo( canSeek: true, diff --git a/lib/models/playback/playback_model.dart b/lib/models/playback/playback_model.dart index 637291a..d0d949f 100644 --- a/lib/models/playback/playback_model.dart +++ b/lib/models/playback/playback_model.dart @@ -2,9 +2,6 @@ import 'dart:developer'; import 'package:chopper/chopper.dart'; import 'package:collection/collection.dart'; -import 'package:fladder/models/items/intro_skip_model.dart'; -import 'package:fladder/models/items/season_model.dart'; -import 'package:fladder/models/items/series_model.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:media_kit/media_kit.dart'; @@ -12,7 +9,10 @@ import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/episode_model.dart'; +import 'package:fladder/models/items/intro_skip_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; +import 'package:fladder/models/items/season_model.dart'; +import 'package:fladder/models/items/series_model.dart'; import 'package:fladder/models/items/trick_play_model.dart'; import 'package:fladder/models/playback/direct_playback_model.dart'; import 'package:fladder/models/playback/offline_playback_model.dart'; @@ -127,8 +127,8 @@ class PlaybackModelHelper { } Future getNextUpEpisode(String itemId) async { - final responnse = await api.showsNextUpGet(parentId: itemId, fields: [ItemFields.overview]); - final episode = responnse.body?.items?.firstOrNull; + final response = await api.showsNextUpGet(parentId: itemId, fields: [ItemFields.overview]); + final episode = response.body?.items?.firstOrNull; if (episode == null) { return null; } else { diff --git a/lib/models/playback/transcode_playback_model.dart b/lib/models/playback/transcode_playback_model.dart index 5ac7edf..dc0d9d3 100644 --- a/lib/models/playback/transcode_playback_model.dart +++ b/lib/models/playback/transcode_playback_model.dart @@ -1,9 +1,4 @@ import 'package:collection/collection.dart'; -import 'package:fladder/models/items/trick_play_model.dart'; -import 'package:fladder/util/list_extensions.dart'; -import 'package:fladder/wrappers/media_control_wrapper.dart' - if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:media_kit/media_kit.dart'; @@ -12,10 +7,15 @@ import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/intro_skip_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; +import 'package:fladder/models/items/trick_play_model.dart'; import 'package:fladder/models/playback/playback_model.dart'; import 'package:fladder/providers/api_provider.dart'; import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/util/duration_extensions.dart'; +import 'package:fladder/util/list_extensions.dart'; +import 'package:fladder/wrappers/media_control_wrapper.dart' + if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart'; +import 'package:flutter/widgets.dart'; class TranscodePlaybackModel implements PlaybackModel { TranscodePlaybackModel({ @@ -148,13 +148,6 @@ class TranscodePlaybackModel implements PlaybackModel { @override Future updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async { final api = ref.read(jellyApiProvider); - - //Check for newly generated scrubImages - if (trickPlay == null) { - final trickplay = await api.getTrickPlay(item: item, ref: ref); - ref.read(playBackModel.notifier).update((state) => copyWith(trickPlay: () => trickplay?.bodyOrThrow)); - } - await api.sessionsPlayingProgressPost( body: PlaybackProgressInfo( canSeek: true, diff --git a/lib/providers/service_provider.dart b/lib/providers/service_provider.dart index e33e3e7..084bd65 100644 --- a/lib/providers/service_provider.dart +++ b/lib/providers/service_provider.dart @@ -4,21 +4,21 @@ import 'dart:typed_data'; import 'package:chopper/chopper.dart'; import 'package:collection/collection.dart'; -import 'package:fladder/jellyfin/enum_models.dart'; -import 'package:fladder/models/credentials_model.dart'; -import 'package:fladder/models/items/intro_skip_model.dart'; -import 'package:fladder/models/items/trick_play_model.dart'; -import 'package:fladder/providers/image_provider.dart'; -import 'package:fladder/util/duration_extensions.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:path/path.dart'; +import 'package:fladder/jellyfin/enum_models.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/account_model.dart'; +import 'package:fladder/models/credentials_model.dart'; import 'package:fladder/models/item_base_model.dart'; +import 'package:fladder/models/items/intro_skip_model.dart'; +import 'package:fladder/models/items/trick_play_model.dart'; import 'package:fladder/providers/api_provider.dart'; +import 'package:fladder/providers/image_provider.dart'; import 'package:fladder/providers/user_provider.dart'; +import 'package:fladder/util/duration_extensions.dart'; import 'package:fladder/util/jellyfin_extension.dart'; -import 'package:path/path.dart'; final jellyServiceProvider = StateProvider( (ref) => JellyService( @@ -913,8 +913,7 @@ class JellyService { if (server == null) return null; - final lines = response.bodyString.split('\n') - ..removeWhere((element) => element.startsWith('#') || !element.contains('.jpg')); + final lines = response.bodyString.split('\n')..removeWhere((element) => element.startsWith('#')); return response.copyWith( body: trickPlayModel.copyWith( images: lines diff --git a/lib/screens/video_player/components/video_progress_bar.dart b/lib/screens/video_player/components/video_progress_bar.dart index a15c39e..f341fa2 100644 --- a/lib/screens/video_player/components/video_progress_bar.dart +++ b/lib/screens/video_player/components/video_progress_bar.dart @@ -13,9 +13,9 @@ import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/widgets/gapped_container_shape.dart'; import 'package:fladder/widgets/shared/fladder_slider.dart'; -import 'package:fladder/widgets/shared/trickplay_image.dart'; +import 'package:fladder/widgets/shared/trick_play_image.dart'; -class ChapterProgressSlider extends ConsumerStatefulWidget { +class VideoProgressBar extends ConsumerStatefulWidget { final Function(bool value) wasPlayingChanged; final bool wasPlaying; final VoidCallback timerReset; @@ -24,7 +24,7 @@ class ChapterProgressSlider extends ConsumerStatefulWidget { final bool buffering; final Duration buffer; final Function(Duration duration) onPositionChanged; - const ChapterProgressSlider({ + const VideoProgressBar({ required this.wasPlayingChanged, required this.wasPlaying, required this.timerReset, @@ -40,7 +40,7 @@ class ChapterProgressSlider extends ConsumerStatefulWidget { ConsumerState createState() => _ChapterProgressSliderState(); } -class _ChapterProgressSliderState extends ConsumerState { +class _ChapterProgressSliderState extends ConsumerState { bool onHoverStart = false; bool onDragStart = false; double _chapterPosition = 0.0; @@ -54,12 +54,12 @@ class _ChapterProgressSliderState extends ConsumerState { final isVisible = (onDragStart ? true : onHoverStart); final player = ref.watch(videoPlayerProvider); final position = onDragStart ? currentDuration : widget.position; - final IntroOutSkipModel? introOutro = ref.read(playBackModel.select((value) => value?.introSkipModel)); + final IntroOutSkipModel? introCreditsModel = ref.read(playBackModel.select((value) => value?.introSkipModel)); final relativeFraction = position.inMilliseconds / widget.duration.inMilliseconds; return LayoutBuilder( builder: (context, constraints) { final sliderHeight = SliderTheme.of(context).trackHeight ?? (constraints.maxHeight / 3); - final bufferWidth = calculateFracionWidth(constraints, widget.buffer); + final bufferWidth = calculateFractionWidth(constraints, widget.buffer); final bufferFraction = relativeFraction / (bufferWidth / constraints.maxWidth); return Stack( clipBehavior: Clip.none, @@ -138,10 +138,10 @@ class _ChapterProgressSliderState extends ConsumerState { child: Stack( alignment: Alignment.center, children: [ - if (introOutro?.intro?.start != null && introOutro?.intro?.end != null) + if (introCreditsModel?.intro?.start != null && introCreditsModel?.intro?.end != null) Positioned( - left: calculateStartOffset(constraints, introOutro!.intro!.start), - right: calculateRightOffset(constraints, introOutro.intro!.end), + left: calculateStartOffset(constraints, introCreditsModel!.intro!.start), + right: calculateRightOffset(constraints, introCreditsModel.intro!.end), bottom: 0, child: Container( height: 6, @@ -153,10 +153,10 @@ class _ChapterProgressSliderState extends ConsumerState { ), ), ), - if (introOutro?.credits?.start != null && introOutro?.credits?.end != null) + if (introCreditsModel?.credits?.start != null && introCreditsModel?.credits?.end != null) Positioned( - left: calculateStartOffset(constraints, introOutro!.credits!.start), - right: calculateRightOffset(constraints, introOutro.credits!.end), + left: calculateStartOffset(constraints, introCreditsModel!.credits!.start), + right: calculateRightOffset(constraints, introCreditsModel.credits!.end), bottom: 0, child: Container( height: 6, @@ -259,7 +259,7 @@ class _ChapterProgressSliderState extends ConsumerState { ); } - double calculateFracionWidth(BoxConstraints constraints, Duration incoming) { + double calculateFractionWidth(BoxConstraints constraints, Duration incoming) { return (constraints.maxWidth * (incoming.inSeconds / widget.duration.inSeconds)).clamp(0, constraints.maxWidth); } @@ -320,7 +320,7 @@ class _ChapterProgressSliderState extends ConsumerState { : const SizedBox.shrink() : AspectRatio( aspectRatio: trickPlay.width.toDouble() / trickPlay.height.toDouble(), - child: TrickplayImage( + child: TrickPlayImage( trickPlay, position: currentDuration, ), diff --git a/lib/screens/video_player/video_player_controls.dart b/lib/screens/video_player/video_player_controls.dart index 87fd741..83d875d 100644 --- a/lib/screens/video_player/video_player_controls.dart +++ b/lib/screens/video_player/video_player_controls.dart @@ -475,7 +475,7 @@ class _DesktopControlsState extends ConsumerState { const SizedBox(height: 4), SizedBox( height: 25, - child: ChapterProgressSlider( + child: VideoProgressBar( wasPlayingChanged: (value) => wasPlaying = value, wasPlaying: wasPlaying, duration: mediaPlayback.duration, diff --git a/lib/widgets/shared/trickplay_image.dart b/lib/widgets/shared/trick_play_image.dart similarity index 81% rename from lib/widgets/shared/trickplay_image.dart rename to lib/widgets/shared/trick_play_image.dart index 48d18d0..ecba7c3 100644 --- a/lib/widgets/shared/trickplay_image.dart +++ b/lib/widgets/shared/trick_play_image.dart @@ -10,19 +10,19 @@ import 'package:http/http.dart' as http; import 'package:fladder/models/items/trick_play_model.dart'; -class TrickplayImage extends ConsumerStatefulWidget { - final TrickPlayModel trickplay; +class TrickPlayImage extends ConsumerStatefulWidget { + final TrickPlayModel trickPlay; final Duration? position; - const TrickplayImage(this.trickplay, {this.position, super.key}); + const TrickPlayImage(this.trickPlay, {this.position, super.key}); @override - ConsumerState createState() => _TrickplayImageState(); + ConsumerState createState() => _TrickPlayImageState(); } -class _TrickplayImageState extends ConsumerState { +class _TrickPlayImageState extends ConsumerState { ui.Image? image; - late TrickPlayModel model = widget.trickplay; + late TrickPlayModel model = widget.trickPlay; late Duration time = widget.position ?? Duration.zero; late Offset currentOffset = const Offset(0, 0); String? currentUrl; @@ -34,11 +34,11 @@ class _TrickplayImageState extends ConsumerState { } @override - void didUpdateWidget(covariant TrickplayImage oldWidget) { + void didUpdateWidget(covariant TrickPlayImage oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.position?.inMilliseconds != widget.position?.inMilliseconds) { time = widget.position ?? Duration.zero; - model = widget.trickplay; + model = widget.trickPlay; loadImage(); } } @@ -48,11 +48,9 @@ class _TrickplayImageState extends ConsumerState { return Container( child: image != null ? CustomPaint( - painter: TilledPainter(image!, currentOffset, widget.trickplay), + painter: _TrickPlayPainter(image!, currentOffset, widget.trickPlay), ) - : Container( - color: Colors.purple, - ), + : const SizedBox.shrink(), ); } @@ -96,12 +94,12 @@ class _TrickplayImageState extends ConsumerState { } } -class TilledPainter extends CustomPainter { +class _TrickPlayPainter extends CustomPainter { final ui.Image image; final Offset offset; final TrickPlayModel model; - TilledPainter(this.image, this.offset, this.model); + _TrickPlayPainter(this.image, this.offset, this.model); @override void paint(Canvas canvas, Size size) {