From 155ac2042e3659d5478570ab168fbcc1d11fafdc Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:00:17 +0100 Subject: [PATCH] feat: audio device selector --- .../sheets/video_settings_sheet.dart | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index 967fb37c..a22a21c7 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -2,9 +2,10 @@ import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; import '../../../services/settings_service.dart'; import '../../../services/sleep_timer_service.dart'; +import '../../../utils/platform_detector.dart'; import '../widgets/sync_offset_control.dart'; -enum _SettingsView { menu, speed, sleep, audioSync, subtitleSync } +enum _SettingsView { menu, speed, sleep, audioSync, subtitleSync, audioDevice } /// Unified settings sheet for playback adjustments with in-sheet navigation class VideoSettingsSheet extends StatefulWidget { @@ -89,6 +90,8 @@ class _VideoSettingsSheetState extends State { return 'Audio Sync'; case _SettingsView.subtitleSync: return 'Subtitle Sync'; + case _SettingsView.audioDevice: + return 'Audio Output'; } } @@ -104,6 +107,8 @@ class _VideoSettingsSheetState extends State { return Icons.sync; case _SettingsView.subtitleSync: return Icons.subtitles; + case _SettingsView.audioDevice: + return Icons.speaker; } } @@ -188,6 +193,7 @@ class _VideoSettingsSheetState extends State { Widget _buildMenuView() { final sleepTimer = SleepTimerService(); + final isDesktop = PlatformDetector.isDesktop(context); return ListView( children: [ @@ -307,6 +313,42 @@ class _VideoSettingsSheetState extends State { ), onTap: () => _navigateTo(_SettingsView.subtitleSync), ), + + // Audio Output Device (Desktop only) + if (isDesktop) + StreamBuilder( + stream: widget.player.stream.audioDevice, + initialData: widget.player.state.audioDevice, + builder: (context, snapshot) { + final currentDevice = snapshot.data ?? widget.player.state.audioDevice; + final deviceLabel = currentDevice.description.isEmpty + ? currentDevice.name + : currentDevice.description; + + return ListTile( + leading: const Icon(Icons.speaker, color: Colors.white70), + title: const Text( + 'Audio Output', + style: TextStyle(color: Colors.white), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + deviceLabel, + style: const TextStyle(color: Colors.white70, fontSize: 14), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 8), + const Icon(Icons.chevron_right, color: Colors.white70), + ], + ), + onTap: () => _navigateTo(_SettingsView.audioDevice), + ); + }, + ), ], ); } @@ -498,6 +540,51 @@ class _VideoSettingsSheetState extends State { ); } + Widget _buildAudioDeviceView() { + return StreamBuilder>( + stream: widget.player.stream.audioDevices, + initialData: widget.player.state.audioDevices, + builder: (context, snapshot) { + final devices = snapshot.data ?? []; + + return StreamBuilder( + stream: widget.player.stream.audioDevice, + initialData: widget.player.state.audioDevice, + builder: (context, selectedSnapshot) { + final currentDevice = selectedSnapshot.data ?? widget.player.state.audioDevice; + + return ListView.builder( + itemCount: devices.length, + itemBuilder: (context, index) { + final device = devices[index]; + final isSelected = device.name == currentDevice.name; + final label = device.description.isEmpty + ? device.name + : device.description; + + return ListTile( + title: Text( + label, + style: TextStyle( + color: isSelected ? Colors.blue : Colors.white, + ), + ), + trailing: isSelected + ? const Icon(Icons.check, color: Colors.blue) + : null, + onTap: () { + widget.player.setAudioDevice(device); + Navigator.pop(context); // Close sheet after selection + }, + ); + }, + ); + }, + ); + }, + ); + } + @override Widget build(BuildContext context) { return SafeArea( @@ -520,6 +607,8 @@ class _VideoSettingsSheetState extends State { return _buildAudioSyncView(); case _SettingsView.subtitleSync: return _buildSubtitleSyncView(); + case _SettingsView.audioDevice: + return _buildAudioDeviceView(); } }(), ),