Files
plezy/lib/widgets/video_controls/sheets/audio_sync_sheet.dart
T

194 lines
6.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import 'package:plezy/services/settings_service.dart';
/// Bottom sheet for adjusting audio sync offset
class AudioSyncSheet extends StatefulWidget {
final Player player;
final int initialOffset;
const AudioSyncSheet({
super.key,
required this.player,
required this.initialOffset,
});
static BoxConstraints getBottomSheetConstraints(BuildContext context) {
final size = MediaQuery.of(context).size;
final isDesktop = size.width > 600;
return BoxConstraints(
maxWidth: isDesktop ? 700 : double.infinity,
maxHeight: isDesktop ? 400 : size.height * 0.75,
minHeight: isDesktop ? 300 : size.height * 0.5,
);
}
static void show(BuildContext context, Player player, int initialOffset) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.grey[900],
isScrollControlled: true,
constraints: getBottomSheetConstraints(context),
builder: (context) =>
AudioSyncSheet(player: player, initialOffset: initialOffset),
);
}
@override
State<AudioSyncSheet> createState() => _AudioSyncSheetState();
}
class _AudioSyncSheetState extends State<AudioSyncSheet> {
late double _currentOffset;
@override
void initState() {
super.initState();
_currentOffset = widget.initialOffset.toDouble();
}
void _applyOffset(double offsetMs) async {
// Convert milliseconds to seconds for media_kit
final offsetSeconds = offsetMs / 1000.0;
// Apply to player using setProperty
await (widget.player.platform as dynamic).setProperty(
'audio-delay',
offsetSeconds.toString(),
);
// Save to settings
final settings = await SettingsService.getInstance();
await settings.setAudioSyncOffset(offsetMs.round());
}
void _resetOffset() {
setState(() {
_currentOffset = 0;
});
_applyOffset(0);
}
String _formatOffset(double offsetMs) {
final sign = offsetMs >= 0 ? '+' : '';
return '$sign${offsetMs.round()}ms';
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.75,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Icon(Icons.sync, color: Colors.white),
const SizedBox(width: 12),
const Text(
'Audio Sync',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
],
),
),
const Divider(color: Colors.white24, height: 1),
Expanded(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Current offset display
Text(
_formatOffset(_currentOffset),
style: const TextStyle(
color: Colors.white,
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
_currentOffset > 0
? 'Audio plays later'
: _currentOffset < 0
? 'Audio plays earlier'
: 'No offset',
style: const TextStyle(
color: Colors.white70,
fontSize: 16,
),
),
const SizedBox(height: 48),
// Slider
Row(
children: [
const Text(
'-2s',
style: TextStyle(color: Colors.white70),
),
Expanded(
child: Slider(
value: _currentOffset,
min: -2000,
max: 2000,
divisions: 80, // 50ms steps
activeColor: Colors.blue,
inactiveColor: Colors.white24,
onChanged: (value) {
setState(() {
_currentOffset = value;
});
},
onChangeEnd: (value) {
_applyOffset(value);
},
),
),
const Text(
'+2s',
style: TextStyle(color: Colors.white70),
),
],
),
const SizedBox(height: 24),
// Reset button
ElevatedButton.icon(
onPressed: _currentOffset != 0 ? _resetOffset : null,
icon: const Icon(Icons.restart_alt),
label: const Text('Reset to 0ms'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[800],
foregroundColor: Colors.white,
disabledBackgroundColor: Colors.grey[850],
disabledForegroundColor: Colors.white38,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
),
),
],
),
),
),
],
),
),
);
}
}