feat: Improve segments visibility logic (#346)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2025-05-17 16:36:33 +02:00 committed by GitHub
parent 1a8765fbd6
commit 947da2390f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 30 additions and 12 deletions

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
@ -39,7 +40,19 @@ class MediaSegment with _$MediaSegment {
bool inRange(Duration position) => (position.compareTo(start) >= 0 && position.compareTo(end) <= 0); bool inRange(Duration position) => (position.compareTo(start) >= 0 && position.compareTo(end) <= 0);
bool forceShow(Duration position) => (position - start).inSeconds < (end - start).inSeconds * 0.20; SegmentVisibility visibility(Duration position, {bool force = false}) {
if (force) return SegmentVisibility.visible;
var difference = (position - start);
if (difference > const Duration(minutes: 1, seconds: 30)) return SegmentVisibility.hidden;
Duration clamp = ((end - start) * 0.20).clamp(Duration.zero, const Duration(minutes: 1));
return difference < clamp ? SegmentVisibility.visible : SegmentVisibility.partially;
}
}
enum SegmentVisibility {
hidden,
partially,
visible;
} }
const Map<MediaSegmentType, SegmentSkip> defaultSegmentSkipValues = { const Map<MediaSegmentType, SegmentSkip> defaultSegmentSkipValues = {

View file

@ -66,13 +66,13 @@ class OpenQueueButton extends ConsumerWidget {
class SkipSegmentButton extends ConsumerWidget { class SkipSegmentButton extends ConsumerWidget {
final MediaSegment? segment; final MediaSegment? segment;
final SegmentSkip? skipType; final SegmentSkip? skipType;
final bool isOverlayVisible; final SegmentVisibility visibility;
final Function() pressedSkip; final Function() pressedSkip;
const SkipSegmentButton({ const SkipSegmentButton({
required this.segment, required this.segment,
this.skipType, this.skipType,
required this.isOverlayVisible, required this.visibility,
required this.pressedSkip, required this.pressedSkip,
super.key, super.key,
}); });
@ -82,7 +82,11 @@ class SkipSegmentButton extends ConsumerWidget {
return AnimatedFadeSize( return AnimatedFadeSize(
child: segment != null && skipType != SegmentSkip.none child: segment != null && skipType != SegmentSkip.none
? AnimatedOpacity( ? AnimatedOpacity(
opacity: isOverlayVisible ? 1 : 0.15, opacity: switch (visibility) {
SegmentVisibility.hidden => 0,
SegmentVisibility.partially => 0.15,
SegmentVisibility.visible => 1.0,
},
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
child: ElevatedButton( child: ElevatedButton(
onPressed: pressedSkip, onPressed: pressedSkip,

View file

@ -168,11 +168,13 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
builder: (context, ref, child) { builder: (context, ref, child) {
final position = ref.watch(mediaPlaybackProvider.select((value) => value.position)); final position = ref.watch(mediaPlaybackProvider.select((value) => value.position));
MediaSegment? segment = mediaSegments?.atPosition(position); MediaSegment? segment = mediaSegments?.atPosition(position);
bool forceShow = segment?.forceShow(position) ?? false; SegmentVisibility forceShow =
segment?.visibility(position, force: showOverlay) ?? SegmentVisibility.hidden;
final segmentSkipType = ref final segmentSkipType = ref
.watch(videoPlayerSettingsProvider.select((value) => value.segmentSkipSettings[segment?.type])); .watch(videoPlayerSettingsProvider.select((value) => value.segmentSkipSettings[segment?.type]));
final autoSkip = final autoSkip = forceShow != SegmentVisibility.hidden &&
forceShow == true && segmentSkipType == SegmentSkip.skip && player.lastState?.buffering == false; segmentSkipType == SegmentSkip.skip &&
player.lastState?.buffering == false;
if (autoSkip) { if (autoSkip) {
skipToSegmentEnd(segment); skipToSegmentEnd(segment);
} }
@ -185,7 +187,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
child: SkipSegmentButton( child: SkipSegmentButton(
segment: segment, segment: segment,
skipType: segmentSkipType, skipType: segmentSkipType,
isOverlayVisible: forceShow ? true : showOverlay, visibility: forceShow,
pressedSkip: () => skipToSegmentEnd(segment), pressedSkip: () => skipToSegmentEnd(segment),
), ),
), ),
@ -441,10 +443,9 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
final List<String?> details = [ final List<String?> details = [
if (AdaptiveLayout.of(context).isDesktop) item?.label(context), if (AdaptiveLayout.of(context).isDesktop) item?.label(context),
mediaPlayback.duration.inMinutes > 1 mediaPlayback.duration.inMinutes > 1
? context.localized.endsAt(DateTime.now().add( ? context.localized.endsAt(DateTime.now().add(Duration(
Duration(milliseconds: milliseconds: (mediaPlayback.duration.inMilliseconds - mediaPlayback.position.inMilliseconds) ~/
(mediaPlayback.duration.inMilliseconds - mediaPlayback.position.inMilliseconds) ~/ ref.read(playbackRateProvider))) ref.read(playbackRateProvider))))
)
: null : null
]; ];
return Column( return Column(