Introduces shared seams for paginated views, D-pad reorder, media control routing, async singletons and the device method channel, then points the open-coded copies at them. Also removes unused models and duplicated provider/server plumbing, folds the twice-implemented artifact store in the server, and factors the repeated Flutter toolchain prologue in CI into a composite action.
140 lines
5.2 KiB
Dart
140 lines
5.2 KiB
Dart
part of '../../video_player_screen.dart';
|
|
|
|
extension _VideoPlayerShaderMethods on VideoPlayerScreenState {
|
|
/// Apply the saved shader preset on playback start.
|
|
/// Reads directly from SettingsService (synchronous SharedPreferences) to
|
|
/// avoid a race with ShaderProvider's async initialization.
|
|
Future<void> _applySavedShaderPreset() async {
|
|
if (_shaderService == null || !_shaderService!.isSupported) return;
|
|
|
|
try {
|
|
final shaderProvider = context.read<ShaderProvider>();
|
|
final settings = await SettingsService.getInstance();
|
|
final presetId = settings.read(SettingsService.globalShaderPreset);
|
|
final preset =
|
|
(shaderProvider.initialized ? shaderProvider.findPresetById(presetId) : ShaderPreset.fromId(presetId)) ??
|
|
ShaderPreset.none;
|
|
await _shaderService!.applyPreset(preset);
|
|
if (!mounted) return;
|
|
shaderProvider.setCurrentPreset(preset);
|
|
} catch (e) {
|
|
appLogger.d('Could not apply shader preset', error: e);
|
|
}
|
|
}
|
|
|
|
/// Enable ambient lighting for the current video/player geometry.
|
|
/// Returns false when the aspect ratios cannot be determined yet.
|
|
Future<bool> _enableAmbientLighting(AmbientLightingService ambientLighting, ShaderProvider shaderProvider) async {
|
|
// Get video display aspect ratio
|
|
final dwidth = await player?.getProperty('dwidth');
|
|
final dheight = await player?.getProperty('dheight');
|
|
if (dwidth == null || dheight == null) return false;
|
|
final w = double.tryParse(dwidth);
|
|
final h = double.tryParse(dheight);
|
|
if (w == null || h == null || h == 0) return false;
|
|
final videoAspect = w / h;
|
|
|
|
// Get player widget aspect ratio
|
|
final playerSize = _videoFilterManager?.playerSize;
|
|
if (playerSize == null || playerSize.height == 0) return false;
|
|
final outputAspect = playerSize.width / playerSize.height;
|
|
|
|
// Clear shaders — ambient lighting and shaders are mutually exclusive
|
|
if (shaderProvider.isShaderEnabled) {
|
|
await _shaderService!.applyPreset(ShaderPreset.none);
|
|
shaderProvider.setCurrentPreset(ShaderPreset.none);
|
|
}
|
|
|
|
// Force contain mode when enabling ambient lighting
|
|
_videoFilterManager?.resetToContain();
|
|
|
|
await ambientLighting.enable(videoAspect, outputAspect);
|
|
return true;
|
|
}
|
|
|
|
/// Restore ambient lighting from persisted setting
|
|
Future<void> _restoreAmbientLighting() async {
|
|
if (!mounted) return;
|
|
|
|
final shaderProvider = context.read<ShaderProvider>();
|
|
final settings = await SettingsService.getInstance();
|
|
if (!mounted) return;
|
|
if (!settings.read(SettingsService.ambientLighting)) return;
|
|
|
|
final ambientLighting = _ambientLightingService;
|
|
if (ambientLighting == null || !ambientLighting.isSupported) return;
|
|
|
|
if (!await _enableAmbientLighting(ambientLighting, shaderProvider)) return;
|
|
if (mounted) _setPlayerState(() {});
|
|
}
|
|
|
|
/// Cycle through BoxFit modes: contain → cover → fill → contain (for button)
|
|
void _cycleBoxFitMode() {
|
|
// Disable ambient lighting when switching boxfit modes
|
|
// (cover/fill change the video rect, making the baked-in shader incorrect)
|
|
_ambientLightingService?.disable();
|
|
_setPlayerState(() {
|
|
_videoFilterManager?.cycleBoxFitMode();
|
|
});
|
|
}
|
|
|
|
void _showZoomToast(double zoomScale) {
|
|
_toastController.show(Symbols.zoom_in_rounded, t.videoControls.zoomPercent(percent: (zoomScale * 100).round()));
|
|
}
|
|
|
|
double _setVideoZoom(double zoomScale, {bool showToast = true}) {
|
|
final filterManager = _videoFilterManager;
|
|
if (filterManager == null) return 1.0;
|
|
|
|
_ambientLightingService?.disable();
|
|
final next = filterManager.setZoomScale(zoomScale);
|
|
if (showToast) _showZoomToast(next);
|
|
if (mounted) _setPlayerState(() {});
|
|
return next;
|
|
}
|
|
|
|
void _zoomVideoIn() {
|
|
final current = _videoFilterManager?.zoomScale ?? 1.0;
|
|
_setVideoZoom(current + VideoFilterManager.zoomStep);
|
|
}
|
|
|
|
void _zoomVideoOut() {
|
|
final current = _videoFilterManager?.zoomScale ?? 1.0;
|
|
_setVideoZoom(current - VideoFilterManager.zoomStep);
|
|
}
|
|
|
|
void _resetVideoZoom() {
|
|
_setVideoZoom(1.0);
|
|
}
|
|
|
|
/// Update video-aspect-override when player size changes.
|
|
/// The shader adapts automatically via built-in target_size uniform.
|
|
void _updateAmbientLightingOnResize(Size newSize) {
|
|
final ambientLighting = _ambientLightingService;
|
|
if (ambientLighting == null || !ambientLighting.isEnabled) return;
|
|
if (newSize.height == 0) return;
|
|
|
|
ambientLighting.updateOutputAspect(newSize.width / newSize.height);
|
|
}
|
|
|
|
/// Toggle ambient lighting effect on/off
|
|
Future<void> _toggleAmbientLighting() async {
|
|
final ambientLighting = _ambientLightingService;
|
|
if (ambientLighting == null || !ambientLighting.isSupported) return;
|
|
final shaderProvider = context.read<ShaderProvider>();
|
|
|
|
if (ambientLighting.isEnabled) {
|
|
await ambientLighting.disable();
|
|
unawaited(_videoFilterManager?.updateVideoFilter());
|
|
} else {
|
|
if (!await _enableAmbientLighting(ambientLighting, shaderProvider)) return;
|
|
}
|
|
|
|
// Persist ambient lighting state
|
|
final settings = await SettingsService.getInstance();
|
|
unawaited(settings.write(SettingsService.ambientLighting, ambientLighting.isEnabled));
|
|
|
|
if (mounted) _setPlayerState(() {});
|
|
}
|
|
}
|