diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index de35581c..18ac50a8 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -789,7 +789,7 @@ repositoryURL = "https://github.com/edde746/MPVKit"; requirement = { kind = revision; - revision = 6f711b36920b37d22e81353dda5a7df7965b1a44; + revision = 60266433556169cf5529a81cc603524ed3e4bec1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8a5bbff4..926ee819 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -40,7 +40,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/edde746/MPVKit", "state" : { - "revision" : "6f711b36920b37d22e81353dda5a7df7965b1a44" + "revision" : "60266433556169cf5529a81cc603524ed3e4bec1" } }, { diff --git a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8a5bbff4..926ee819 100644 --- a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -40,7 +40,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/edde746/MPVKit", "state" : { - "revision" : "6f711b36920b37d22e81353dda5a7df7965b1a44" + "revision" : "60266433556169cf5529a81cc603524ed3e4bec1" } }, { diff --git a/lib/services/video_filter_manager.dart b/lib/services/video_filter_manager.dart index be410bac..82bda589 100644 --- a/lib/services/video_filter_manager.dart +++ b/lib/services/video_filter_manager.dart @@ -78,14 +78,22 @@ class VideoFilterManager { Size? get playerSize => _playerSize; - static double videoZoomPropertyForScale(double scale) { + static double normalizeZoomScale(double scale) { final clamped = scale.clamp(minZoomScale, maxZoomScale).toDouble(); - return math.log(clamped) / math.ln2; + final percent = (clamped * 100).round(); + if (percent == 100) return 1.0; + return percent / 100; + } + + static double videoZoomPropertyForScale(double scale) { + final normalized = normalizeZoomScale(scale); + if (normalized == 1.0) return 0.0; + return math.log(normalized) / math.ln2; } double setZoomScale(double scale) { - final next = scale.clamp(minZoomScale, maxZoomScale).toDouble(); - if ((_zoomScale - next).abs() < 0.0001) return _zoomScale; + final next = normalizeZoomScale(scale); + if (_zoomScale == next) return _zoomScale; _zoomScale = next; updateVideoFilter(); return _zoomScale; @@ -140,7 +148,7 @@ class VideoFilterManager { shouldUpdate = true; } if (_prePipZoomScale != null) { - _zoomScale = _prePipZoomScale!; + _zoomScale = normalizeZoomScale(_prePipZoomScale!); _prePipZoomScale = null; shouldUpdate = true; } diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index df02e771..6a5acadb 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -215,15 +215,16 @@ class _VideoSettingsSheetState extends State { super.initState(); _audioSyncOffset = widget.audioSyncOffset; _subtitleSyncOffset = widget.subtitleSyncOffset; - _zoomScale = widget.videoZoomScale; + _zoomScale = VideoFilterManager.normalizeZoomScale(widget.videoZoomScale); _loadDebugDvConversionMode(); } @override void didUpdateWidget(covariant VideoSettingsSheet oldWidget) { super.didUpdateWidget(oldWidget); - if ((widget.videoZoomScale - oldWidget.videoZoomScale).abs() > 0.0001) { - _zoomScale = widget.videoZoomScale; + final nextZoomScale = VideoFilterManager.normalizeZoomScale(widget.videoZoomScale); + if (_zoomScale != nextZoomScale) { + _zoomScale = nextZoomScale; } } @@ -392,7 +393,7 @@ class _VideoSettingsSheetState extends State { String _formatZoomScale(double scale) => '${(scale * 100).round()}%'; void _setZoomScale(double scale) { - final next = scale.clamp(VideoFilterManager.minZoomScale, VideoFilterManager.maxZoomScale).toDouble(); + final next = VideoFilterManager.normalizeZoomScale(scale); setState(() { _zoomScale = next; }); diff --git a/test/services/video_filter_manager_test.dart b/test/services/video_filter_manager_test.dart index dff56bc4..4c1ee62e 100644 --- a/test/services/video_filter_manager_test.dart +++ b/test/services/video_filter_manager_test.dart @@ -4,6 +4,48 @@ import 'package:plezy/mpv/mpv.dart'; import 'package:plezy/services/video_filter_manager.dart'; void main() { + test('zoom scale snaps to whole percentages', () { + final player = _RecordingPlayer(); + final manager = VideoFilterManager(player: player, availableVersions: const [], selectedMediaIndex: 0); + addTearDown(manager.dispose); + + expect(manager.setZoomScale(1.234), 1.23); + expect(manager.zoomScale, 1.23); + + expect(manager.adjustZoom(VideoFilterManager.zoomStep), 1.24); + expect(manager.zoomScale, 1.24); + }); + + test('zoom scale snaps near 100 percent to exact default', () { + final player = _RecordingPlayer(); + final manager = VideoFilterManager(player: player, availableVersions: const [], selectedMediaIndex: 0); + addTearDown(manager.dispose); + + manager.setZoomScale(1.5); + + expect(manager.setZoomScale(1.00008), 1.0); + expect(manager.zoomScale, 1.0); + expect(manager.resetZoom(), 1.0); + }); + + test('video zoom property is exact zero at normalized default', () async { + final player = _RecordingPlayer(); + final manager = VideoFilterManager(player: player, availableVersions: const [], selectedMediaIndex: 0); + addTearDown(manager.dispose); + + expect(VideoFilterManager.videoZoomPropertyForScale(1.00008), 0.0); + + manager.setZoomScale(1.00008); + await Future.delayed(Duration.zero); + player.writes.clear(); + + await manager.updateVideoFilter(); + + final zoomWrites = player.writes.where((write) => write.key == 'video-zoom').toList(); + expect(zoomWrites, isNotEmpty); + expect(zoomWrites.last.value, '0.0'); + }); + test('stretch mode applies the initial player size before a resize event', () async { final player = _RecordingPlayer(); final manager = VideoFilterManager(