Files
Ensemble/lib/services/hardware_volume_service.dart
ChrisandClaude Opus 4.5 3b5a8631a8 Change package ID to com.collotsspot.ensemble
BREAKING CHANGE: Users must uninstall and reinstall the app.
The previous package ID (com.musicassistant.music_assistant) could be
confused with an official Music Assistant app.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 19:55:23 +00:00

91 lines
2.9 KiB
Dart

import 'dart:async';
import 'package:flutter/services.dart';
import 'debug_logger.dart';
/// Service to intercept hardware volume button presses
/// and route them to the selected Music Assistant player.
///
/// For MA players: intercept buttons and send volume commands via API
/// For builtin player: let native volume work (don't intercept)
class HardwareVolumeService {
static final HardwareVolumeService _instance = HardwareVolumeService._internal();
factory HardwareVolumeService() => _instance;
HardwareVolumeService._internal();
static const _channel = MethodChannel('com.collotsspot.ensemble/volume_buttons');
final _logger = DebugLogger();
final _volumeUpController = StreamController<void>.broadcast();
final _volumeDownController = StreamController<void>.broadcast();
Stream<void> get onVolumeUp => _volumeUpController.stream;
Stream<void> get onVolumeDown => _volumeDownController.stream;
bool _isListening = false;
bool get isListening => _isListening;
bool _isIntercepting = false;
bool get isIntercepting => _isIntercepting;
/// Initialize the service and start listening for volume button events
Future<void> init() async {
if (_isListening) return;
_channel.setMethodCallHandler((call) async {
switch (call.method) {
case 'volumeUp':
_volumeUpController.add(null);
break;
case 'volumeDown':
_volumeDownController.add(null);
break;
}
});
try {
await _channel.invokeMethod('startListening');
_isListening = true;
_isIntercepting = true;
} catch (e) {
_logger.error('Failed to start volume button listening', context: 'VolumeService', error: e);
}
}
/// Enable or disable volume button interception.
/// When disabled, hardware volume buttons control device volume normally.
/// When enabled, volume button events are sent to Flutter for MA player control.
Future<void> setIntercepting(bool intercept) async {
if (!_isListening || _isIntercepting == intercept) return;
try {
if (intercept) {
await _channel.invokeMethod('startListening');
_isIntercepting = true;
} else {
await _channel.invokeMethod('stopListening');
_isIntercepting = false;
}
} catch (e) {
_logger.error('Failed to set volume interception', context: 'VolumeService', error: e);
}
}
/// Stop listening for volume button events
Future<void> dispose() async {
if (!_isListening) return;
try {
await _channel.invokeMethod('stopListening');
_isListening = false;
_isIntercepting = false;
_logger.info('Hardware volume button listening stopped', context: 'VolumeService');
} catch (e) {
_logger.error('Failed to stop volume button listening', context: 'VolumeService', error: e);
}
// Close StreamControllers to prevent memory leaks
await _volumeUpController.close();
await _volumeDownController.close();
}
}