133 lines
4.2 KiB
Dart
133 lines
4.2 KiB
Dart
import 'dart:io';
|
|
|
|
import '../models/shader_preset.dart';
|
|
import '../mpv/player/player.dart';
|
|
import '../utils/app_logger.dart';
|
|
import 'ambient_lighting_service.dart';
|
|
import 'shader_asset_loader.dart';
|
|
|
|
/// Service for applying GLSL shaders to the MPV video player.
|
|
///
|
|
/// Handles shader chain building, HDR detection, and runtime switching.
|
|
/// When ambient lighting is active, the ambient lighting shader is always appended last
|
|
/// in the chain after any upscaling/processing shaders.
|
|
class ShaderService {
|
|
final Player _player;
|
|
ShaderPreset _currentPreset = ShaderPreset.none;
|
|
|
|
/// Reference to ambient lighting service for re-appending its shader after chain rebuilds.
|
|
AmbientLightingService? ambientLightingService;
|
|
|
|
ShaderService(this._player);
|
|
|
|
ShaderPreset get currentPreset => _currentPreset;
|
|
|
|
static bool get isPlatformSupported => !Platform.isIOS && !Platform.isMacOS;
|
|
|
|
/// Check if the player is MPV (shaders are MPV-only)
|
|
bool get isSupported => _player.playerType == 'mpv' && isPlatformSupported;
|
|
|
|
/// Apply a shader preset to the video player.
|
|
///
|
|
/// For NVScaler with auto-HDR skip enabled, will check video colorspace
|
|
/// and skip shader application for HDR content.
|
|
Future<void> applyPreset(ShaderPreset preset) async {
|
|
if (!isSupported) {
|
|
appLogger.d('ShaderService: Shaders not supported on ${_player.playerType}');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (preset.type == ShaderPresetType.nvscaler && preset.nvscalerConfig?.autoHdrSkip == true) {
|
|
final isHdr = await _isHdrContent();
|
|
if (isHdr) {
|
|
appLogger.d('ShaderService: Skipping NVScaler on HDR content');
|
|
await _clearShaders();
|
|
_currentPreset = ShaderPreset.none;
|
|
await _reappendAmbientLighting();
|
|
return;
|
|
}
|
|
}
|
|
|
|
final shaderPaths = await ShaderAssetLoader.getShadersForPreset(preset);
|
|
|
|
if (shaderPaths.isEmpty) {
|
|
// No shaders - clear any existing ones
|
|
await _clearShaders();
|
|
_currentPreset = preset;
|
|
await _reappendAmbientLighting();
|
|
return;
|
|
}
|
|
|
|
await _clearShaders();
|
|
|
|
for (final shaderPath in shaderPaths) {
|
|
await _player.command(['change-list', 'glsl-shaders', 'append', shaderPath]);
|
|
}
|
|
|
|
_currentPreset = preset;
|
|
|
|
// Re-append ambient lighting shader at end of chain
|
|
await _reappendAmbientLighting();
|
|
|
|
appLogger.d('ShaderService: Applied ${preset.name} with ${shaderPaths.length} shaders');
|
|
} catch (e, st) {
|
|
appLogger.w('ShaderService: Failed to apply preset', error: e, stackTrace: st);
|
|
// Don't rethrow - shader failure shouldn't stop playback
|
|
}
|
|
}
|
|
|
|
Future<void> _clearShaders() async {
|
|
try {
|
|
await _player.command(['change-list', 'glsl-shaders', 'clr', '']);
|
|
} catch (e, st) {
|
|
appLogger.w('ShaderService: Failed to clear shaders', error: e, stackTrace: st);
|
|
}
|
|
}
|
|
|
|
/// Re-append the ambient lighting shader if it's active.
|
|
/// Called after shader chain rebuilds to keep ambient lighting last.
|
|
Future<void> _reappendAmbientLighting() async {
|
|
final service = ambientLightingService;
|
|
if (service == null || !service.isEnabled) return;
|
|
|
|
try {
|
|
await service.reappendShader();
|
|
} catch (e, st) {
|
|
appLogger.w('ShaderService: Failed to re-append ambient lighting', error: e, stackTrace: st);
|
|
}
|
|
}
|
|
|
|
/// Check if the current video content is HDR.
|
|
Future<bool> _isHdrContent() async {
|
|
try {
|
|
// Check video color matrix for BT.2020 (HDR indicator)
|
|
final colormatrix = await _player.getProperty('video-params/colormatrix');
|
|
if (colormatrix?.contains('bt.2020') == true) {
|
|
return true;
|
|
}
|
|
|
|
// Also check color primaries
|
|
final primaries = await _player.getProperty('video-params/primaries');
|
|
if (primaries?.contains('bt.2020') == true) {
|
|
return true;
|
|
}
|
|
|
|
// Check for HDR transfer characteristics
|
|
final gamma = await _player.getProperty('video-params/gamma');
|
|
if (gamma?.contains('pq') == true || gamma?.contains('hlg') == true) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (e) {
|
|
appLogger.d('ShaderService: HDR detection failed', error: e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<void> disable() async {
|
|
await applyPreset(ShaderPreset.none);
|
|
}
|
|
}
|