feat: add ability to select current device's output speaker

This commit is contained in:
Kingkor Roy Tirtho 2024-04-04 22:19:10 +06:00
parent 83ac24ac02
commit 36d6a1d8a8
8 changed files with 207 additions and 64 deletions

View File

@ -120,4 +120,5 @@ abstract class SpotubeIcons {
static const speaker = FeatherIcons.speaker; static const speaker = FeatherIcons.speaker;
static const monitor = FeatherIcons.monitor; static const monitor = FeatherIcons.monitor;
static const power = FeatherIcons.power; static const power = FeatherIcons.power;
static const bluetooth = FeatherIcons.bluetooth;
} }

View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
class ConnectPageLocalDevices extends HookWidget {
const ConnectPageLocalDevices({super.key});
@override
Widget build(BuildContext context) {
final ThemeData(:textTheme) = Theme.of(context);
final devicesFuture = useFuture(audioPlayer.devices);
final devicesStream = useStream(audioPlayer.devicesStream);
final selectedDeviceFuture = useFuture(audioPlayer.selectedDevice);
final selectedDeviceStream = useStream(audioPlayer.selectedDeviceStream);
final devices = devicesStream.data ?? devicesFuture.data;
final selectedDevice =
selectedDeviceStream.data ?? selectedDeviceFuture.data;
if (devices == null) {
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
return SliverMainAxisGroup(
slivers: [
const SliverGap(10),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
sliver: SliverToBoxAdapter(
child: Text(
context.l10n.this_device,
style: textTheme.titleMedium,
),
),
),
const SliverGap(10),
SliverList.separated(
itemCount: devices.length,
separatorBuilder: (context, index) => const Gap(10),
itemBuilder: (context, index) {
final device = devices[index];
return Card(
child: ListTile(
leading: const Icon(SpotubeIcons.speaker),
title: Text(device.description),
subtitle: Text(device.name),
selected: selectedDevice == device,
onTap: () => audioPlayer.setAudioDevice(device),
),
);
},
),
],
);
}
}

View File

@ -318,5 +318,7 @@
"enable_connect_description": "Control Spotube from other devices", "enable_connect_description": "Control Spotube from other devices",
"devices": "Devices", "devices": "Devices",
"select": "Select", "select": "Select",
"connect_client_alert": "You're being controlled by {client}" "connect_client_alert": "You're being controlled by {client}",
"this_device": "This Device",
"remote": "Remote"
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/connect/local_devices.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/provider/connect/clients.dart';
@ -12,7 +13,7 @@ class ConnectPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final ThemeData(:colorScheme) = Theme.of(context); final ThemeData(:colorScheme, :textTheme) = Theme.of(context);
final connectClients = ref.watch(connectClientsProvider); final connectClients = ref.watch(connectClientsProvider);
final connectClientsNotifier = ref.read(connectClientsProvider.notifier); final connectClientsNotifier = ref.read(connectClientsProvider.notifier);
@ -23,14 +24,33 @@ class ConnectPage extends HookConsumerWidget {
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
title: Text(context.l10n.devices), title: Text(context.l10n.devices),
), ),
body: ListView.separated( body: ListTileTheme(
padding: const EdgeInsets.all(10), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
selectedTileColor: colorScheme.secondary.withOpacity(0.1),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: CustomScrollView(
slivers: [
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
sliver: SliverToBoxAdapter(
child: Text(
context.l10n.remote,
style: textTheme.titleMedium,
),
),
),
const SliverGap(10),
SliverList.separated(
itemCount: discoveredDevices?.length ?? 0, itemCount: discoveredDevices?.length ?? 0,
separatorBuilder: (context, index) => const Gap(10), separatorBuilder: (context, index) => const Gap(10),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final device = discoveredDevices![index]; final device = discoveredDevices![index];
final selected = final selected =
connectClients.asData?.value.resolvedService?.name == device.name; connectClients.asData?.value.resolvedService?.name ==
device.name;
return Card( return Card(
child: ListTile( child: ListTile(
leading: const Icon(SpotubeIcons.monitor), leading: const Icon(SpotubeIcons.monitor),
@ -42,10 +62,6 @@ class ConnectPage extends HookConsumerWidget {
) )
: null, : null,
selected: selected, selected: selected,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
selectedTileColor: colorScheme.secondary.withOpacity(0.1),
onTap: () { onTap: () {
if (selected) { if (selected) {
ServiceUtils.push( ServiceUtils.push(
@ -67,6 +83,11 @@ class ConnectPage extends HookConsumerWidget {
); );
}, },
), ),
const ConnectPageLocalDevices(),
],
),
),
),
); );
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:catcher_2/catcher_2.dart'; import 'package:catcher_2/catcher_2.dart';
import 'package:media_kit/media_kit.dart';
import 'package:spotube/services/audio_player/mk_state_player.dart'; import 'package:spotube/services/audio_player/mk_state_player.dart';
// import 'package:just_audio/just_audio.dart' as ja; // import 'package:just_audio/just_audio.dart' as ja;
import 'dart:async'; import 'dart:async';
@ -14,7 +15,7 @@ part 'audio_player_impl.dart';
abstract class AudioPlayerInterface { abstract class AudioPlayerInterface {
final MkPlayerWithState _mkPlayer; final MkPlayerWithState _mkPlayer;
// final ja.AudioPlayer? _justAudio; // final ja.AudioPlayer? _justAudxio;
AudioPlayerInterface() AudioPlayerInterface()
: _mkPlayer = MkPlayerWithState( : _mkPlayer = MkPlayerWithState(
@ -60,6 +61,14 @@ abstract class AudioPlayerInterface {
} }
} }
Future<AudioDevice> get selectedDevice async {
return _mkPlayer.state.audioDevice;
}
Future<List<AudioDevice>> get devices async {
return _mkPlayer.state.audioDevices;
}
bool get hasSource { bool get hasSource {
return _mkPlayer.playlist.medias.isNotEmpty; return _mkPlayer.playlist.medias.isNotEmpty;
// if (mkSupportedPlatform) { // if (mkSupportedPlatform) {

View File

@ -83,6 +83,10 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
// await _justAudio?.setSpeed(speed); // await _justAudio?.setSpeed(speed);
} }
Future<void> setAudioDevice(AudioDevice device) async {
await _mkPlayer.setAudioDevice(device);
}
Future<void> dispose() async { Future<void> dispose() async {
await _mkPlayer.dispose(); await _mkPlayer.dispose();
// await _justAudio?.dispose(); // await _justAudio?.dispose();

View File

@ -140,4 +140,10 @@ mixin SpotubeAudioPlayersStreams on AudioPlayerInterface {
// .cast<String>(); // .cast<String>();
// } // }
} }
Stream<List<AudioDevice>> get devicesStream =>
_mkPlayer.stream.audioDevices.asBroadcastStream();
Stream<AudioDevice> get selectedDeviceStream =>
_mkPlayer.stream.audioDevice.asBroadcastStream();
} }

View File

@ -4,7 +4,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"bn": [ "bn": [
@ -12,7 +14,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"ca": [ "ca": [
@ -20,7 +24,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"de": [ "de": [
@ -28,7 +34,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"es": [ "es": [
@ -36,7 +44,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"fa": [ "fa": [
@ -44,7 +54,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"fr": [ "fr": [
@ -52,7 +64,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"hi": [ "hi": [
@ -60,7 +74,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"it": [ "it": [
@ -68,7 +84,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"ja": [ "ja": [
@ -76,7 +94,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"ko": [ "ko": [
@ -84,7 +104,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"ne": [ "ne": [
@ -92,7 +114,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"nl": [ "nl": [
@ -100,7 +124,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"pl": [ "pl": [
@ -108,7 +134,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"pt": [ "pt": [
@ -116,7 +144,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"ru": [ "ru": [
@ -124,7 +154,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"tr": [ "tr": [
@ -132,7 +164,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"uk": [ "uk": [
@ -140,7 +174,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"vi": [ "vi": [
@ -150,7 +186,9 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
], ],
"zh": [ "zh": [
@ -158,6 +196,8 @@
"enable_connect_description", "enable_connect_description",
"devices", "devices",
"select", "select",
"connect_client_alert" "connect_client_alert",
"this_device",
"remote"
] ]
} }