2f5edfc512
- Added `dartamp_skin` as a local dependency in `pubspec.yaml`. - Updated `path_provider` to version ^2.1.5. - Modified `test_driver/app.dart` to initialize `SkinManager` and pass it to `DartampApp`. - Updated `generated_plugins.cmake` to include `jni` in the FFI plugin list. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
332 lines
8.1 KiB
Dart
332 lines
8.1 KiB
Dart
import "dart:async";
|
|
|
|
import "package:flutter/foundation.dart";
|
|
import "package:shared_preferences/shared_preferences.dart";
|
|
|
|
const String _kWindowShadeKey = "winamp.window_shade";
|
|
const String _kAlwaysOnTopKey = "winamp.always_on_top";
|
|
const String _kEqOpenKey = "winamp.eq_open";
|
|
const String _kEqShadeKey = "winamp.eq_shade";
|
|
const String _kPlOpenKey = "winamp.pl_open";
|
|
const String _kPlShadeKey = "winamp.pl_shade";
|
|
const String _kUiScaleKey = "winamp.ui_scale";
|
|
const String _kActiveSkinKey = "winamp.active_skin";
|
|
|
|
/// Simulated playback/UI state for the player, mirroring the flags tracked by
|
|
/// the original Winamp main window. No audio yet; the transport simply drives
|
|
/// the display, sliders, and windowshade.
|
|
class PlayerState extends ChangeNotifier {
|
|
PlayerState({
|
|
this.windowShade = false,
|
|
this.alwaysOnTop = false,
|
|
this.eqOpen = false,
|
|
this.eqShade = false,
|
|
this.plOpen = false,
|
|
this.plShade = false,
|
|
double uiScale = 1.0,
|
|
}) : _uiScale = uiScale.clamp(1.0, 3.0);
|
|
|
|
bool playing = false;
|
|
bool paused = false;
|
|
bool shuffle = false;
|
|
bool repeat = false;
|
|
bool eqOpen = false;
|
|
bool eqShade = false;
|
|
bool plOpen = false;
|
|
bool plShade = false;
|
|
bool windowShade = false;
|
|
bool alwaysOnTop = false;
|
|
bool active = true;
|
|
|
|
double _uiScale = 1.0;
|
|
|
|
/// The window scaling factor (1.0, 2.0, or 3.0) from the Window menu.
|
|
double get uiScale => _uiScale;
|
|
|
|
/// 0..255, volume.bmp row index maps `volume * 27 / 255`.
|
|
int volume = 200;
|
|
|
|
/// -127..127; balance.bmp row maps `abs(balance) * 27 / 127`.
|
|
int balance = 0;
|
|
|
|
/// 0..256 seek position, posbar thumb maps directly.
|
|
int position = 0;
|
|
|
|
int durationSec = 205;
|
|
int bitrate = 128;
|
|
int mixrate = 44;
|
|
String title = "Winamp 5.9";
|
|
String fileName = "dartamp";
|
|
int playIcon = 2;
|
|
|
|
/// 0..64 per band, 32 = 0 dB (mirrors the original EQ slider state).
|
|
int preamp = 32;
|
|
|
|
/// The 10 band gains, index 0 = 60 Hz through 9 = 16 kHz.
|
|
final List<int> eqBands = List.filled(10, 32);
|
|
|
|
/// Whether the Equalizer is enabled (the ON button).
|
|
bool eqEnabled = true;
|
|
|
|
/// Whether the Equalizer presets auto-load per song (the AUTO button).
|
|
bool eqAuto = false;
|
|
|
|
Timer? _ticker;
|
|
|
|
void togglePlay() {
|
|
if (paused) {
|
|
paused = false;
|
|
playing = true;
|
|
_startTicker();
|
|
} else {
|
|
playing = !playing;
|
|
paused = false;
|
|
playIcon = playing ? 0 : 2;
|
|
if (playing) {
|
|
_startTicker();
|
|
} else {
|
|
_stopTicker();
|
|
}
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
void pause() {
|
|
paused = true;
|
|
playing = false;
|
|
playIcon = 1;
|
|
notifyListeners();
|
|
}
|
|
|
|
void stop() {
|
|
playing = false;
|
|
paused = false;
|
|
position = 0;
|
|
playIcon = 2;
|
|
_stopTicker();
|
|
notifyListeners();
|
|
}
|
|
|
|
void next() {}
|
|
|
|
void prev() {}
|
|
|
|
void toggleShuffle() {
|
|
shuffle = !shuffle;
|
|
notifyListeners();
|
|
}
|
|
|
|
void toggleRepeat() {
|
|
repeat = !repeat;
|
|
notifyListeners();
|
|
}
|
|
|
|
void setVolume(int value) {
|
|
volume = value.clamp(0, 255);
|
|
notifyListeners();
|
|
}
|
|
|
|
void setBalance(int value) {
|
|
balance = value.clamp(-127, 127);
|
|
notifyListeners();
|
|
}
|
|
|
|
void setSeek(int value) {
|
|
position = value.clamp(0, 256);
|
|
notifyListeners();
|
|
}
|
|
|
|
void setPreamp(int value) {
|
|
final clamped = value.clamp(0, 64);
|
|
if (clamped == preamp) return;
|
|
preamp = clamped;
|
|
notifyListeners();
|
|
}
|
|
|
|
void setEqBand(int index, int value) {
|
|
if (index < 0 || index >= eqBands.length) return;
|
|
final clamped = value.clamp(0, 64);
|
|
if (clamped == eqBands[index]) return;
|
|
eqBands[index] = clamped;
|
|
notifyListeners();
|
|
}
|
|
|
|
void setEqEnabled(bool value) {
|
|
if (eqEnabled == value) return;
|
|
eqEnabled = value;
|
|
notifyListeners();
|
|
}
|
|
|
|
void setEqAuto(bool value) {
|
|
if (eqAuto == value) return;
|
|
eqAuto = value;
|
|
notifyListeners();
|
|
}
|
|
|
|
void toggleEq() {
|
|
setEqOpen(!eqOpen);
|
|
}
|
|
|
|
void setEqOpen(bool value) {
|
|
if (eqOpen == value) return;
|
|
eqOpen = value;
|
|
unawaited(_saveEqOpen(value));
|
|
notifyListeners();
|
|
}
|
|
|
|
void togglePlaylist() {
|
|
setPlOpen(!plOpen);
|
|
}
|
|
|
|
void setPlOpen(bool value) {
|
|
if (plOpen == value) return;
|
|
plOpen = value;
|
|
unawaited(_savePlOpen(value));
|
|
notifyListeners();
|
|
}
|
|
|
|
void toggleWindowShade() {
|
|
windowShade = !windowShade;
|
|
unawaited(_saveWindowShade(windowShade));
|
|
notifyListeners();
|
|
}
|
|
|
|
void toggleAlwaysOnTop() {
|
|
setAlwaysOnTop(!alwaysOnTop);
|
|
}
|
|
|
|
void setAlwaysOnTop(bool value) {
|
|
if (alwaysOnTop == value) return;
|
|
alwaysOnTop = value;
|
|
unawaited(_saveAlwaysOnTop(value));
|
|
notifyListeners();
|
|
}
|
|
|
|
void setUiScale(double value) {
|
|
final clamped = value.clamp(1.0, 3.0);
|
|
if (clamped == _uiScale) return;
|
|
_uiScale = clamped;
|
|
unawaited(_saveUiScale(clamped));
|
|
notifyListeners();
|
|
}
|
|
|
|
void toggleEqShade() {
|
|
eqShade = !eqShade;
|
|
unawaited(_saveEqShade(eqShade));
|
|
notifyListeners();
|
|
}
|
|
|
|
void togglePlShade() {
|
|
plShade = !plShade;
|
|
unawaited(_savePlShade(plShade));
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> _saveWindowShade(bool value) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_kWindowShadeKey, value);
|
|
}
|
|
|
|
Future<void> _saveAlwaysOnTop(bool value) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_kAlwaysOnTopKey, value);
|
|
}
|
|
|
|
Future<void> _saveEqOpen(bool value) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_kEqOpenKey, value);
|
|
}
|
|
|
|
Future<void> _saveEqShade(bool value) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_kEqShadeKey, value);
|
|
}
|
|
|
|
Future<void> _savePlOpen(bool value) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_kPlOpenKey, value);
|
|
}
|
|
|
|
Future<void> _savePlShade(bool value) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_kPlShadeKey, value);
|
|
}
|
|
|
|
Future<void> _saveUiScale(double value) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setDouble(_kUiScaleKey, value);
|
|
}
|
|
|
|
static Future<bool> loadWindowShade() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getBool(_kWindowShadeKey) ?? false;
|
|
}
|
|
|
|
static Future<bool> loadAlwaysOnTop() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getBool(_kAlwaysOnTopKey) ?? false;
|
|
}
|
|
|
|
static Future<bool> loadEqOpen() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getBool(_kEqOpenKey) ?? false;
|
|
}
|
|
|
|
static Future<bool> loadEqShade() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getBool(_kEqShadeKey) ?? false;
|
|
}
|
|
|
|
static Future<bool> loadPlOpen() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getBool(_kPlOpenKey) ?? false;
|
|
}
|
|
|
|
static Future<bool> loadPlShade() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getBool(_kPlShadeKey) ?? false;
|
|
}
|
|
|
|
static Future<double> loadUiScale() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return (prefs.getDouble(_kUiScaleKey) ?? 1.0).clamp(1.0, 3.0);
|
|
}
|
|
|
|
/// The name of the active skin, or `null` when the default (Base) applies.
|
|
static Future<String?> loadActiveSkin() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getString(_kActiveSkinKey);
|
|
}
|
|
|
|
/// Persists the active skin name; `null` restores the default.
|
|
static Future<void> saveActiveSkin(String? name) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
if (name == null) {
|
|
await prefs.remove(_kActiveSkinKey);
|
|
} else {
|
|
await prefs.setString(_kActiveSkinKey, name);
|
|
}
|
|
}
|
|
|
|
void _startTicker() {
|
|
_ticker?.cancel();
|
|
_ticker = Timer.periodic(const Duration(seconds: 1), (_) {
|
|
final nextSec = position * durationSec ~/ 256 + 1;
|
|
position = nextSec >= durationSec ? 0 : nextSec * 256 ~/ durationSec;
|
|
playIcon = 0;
|
|
notifyListeners();
|
|
});
|
|
}
|
|
|
|
void _stopTicker() {
|
|
_ticker?.cancel();
|
|
_ticker = null;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_stopTicker();
|
|
super.dispose();
|
|
}
|
|
}
|