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 monitor = FeatherIcons.monitor;
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",
"devices": "Devices",
"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:hooks_riverpod/hooks_riverpod.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/extensions/context.dart';
import 'package:spotube/provider/connect/clients.dart';
@ -12,7 +13,7 @@ class ConnectPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final ThemeData(:colorScheme) = Theme.of(context);
final ThemeData(:colorScheme, :textTheme) = Theme.of(context);
final connectClients = ref.watch(connectClientsProvider);
final connectClientsNotifier = ref.read(connectClientsProvider.notifier);
@ -23,49 +24,69 @@ class ConnectPage extends HookConsumerWidget {
automaticallyImplyLeading: true,
title: Text(context.l10n.devices),
),
body: ListView.separated(
padding: const EdgeInsets.all(10),
itemCount: discoveredDevices?.length ?? 0,
separatorBuilder: (context, index) => const Gap(10),
itemBuilder: (context, index) {
final device = discoveredDevices![index];
final selected =
connectClients.asData?.value.resolvedService?.name == device.name;
return Card(
child: ListTile(
leading: const Icon(SpotubeIcons.monitor),
title: Text(device.name),
subtitle: selected
? Text(
"${connectClients.asData?.value.resolvedService?.host}"
":${connectClients.asData?.value.resolvedService?.port}",
)
: null,
selected: selected,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
body: ListTileTheme(
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,
),
),
),
selectedTileColor: colorScheme.secondary.withOpacity(0.1),
onTap: () {
if (selected) {
ServiceUtils.push(
context,
"/connect/control",
const SliverGap(10),
SliverList.separated(
itemCount: discoveredDevices?.length ?? 0,
separatorBuilder: (context, index) => const Gap(10),
itemBuilder: (context, index) {
final device = discoveredDevices![index];
final selected =
connectClients.asData?.value.resolvedService?.name ==
device.name;
return Card(
child: ListTile(
leading: const Icon(SpotubeIcons.monitor),
title: Text(device.name),
subtitle: selected
? Text(
"${connectClients.asData?.value.resolvedService?.host}"
":${connectClients.asData?.value.resolvedService?.port}",
)
: null,
selected: selected,
onTap: () {
if (selected) {
ServiceUtils.push(
context,
"/connect/control",
);
} else {
connectClientsNotifier.resolveService(device);
}
},
trailing: selected
? IconButton(
icon: const Icon(SpotubeIcons.power),
onPressed: () =>
connectClientsNotifier.clearResolvedService(),
)
: null,
),
);
} else {
connectClientsNotifier.resolveService(device);
}
},
trailing: selected
? IconButton(
icon: const Icon(SpotubeIcons.power),
onPressed: () =>
connectClientsNotifier.clearResolvedService(),
)
: null,
),
);
},
},
),
const ConnectPageLocalDevices(),
],
),
),
),
);
}

View File

@ -1,4 +1,5 @@
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:just_audio/just_audio.dart' as ja;
import 'dart:async';
@ -14,7 +15,7 @@ part 'audio_player_impl.dart';
abstract class AudioPlayerInterface {
final MkPlayerWithState _mkPlayer;
// final ja.AudioPlayer? _justAudio;
// final ja.AudioPlayer? _justAudxio;
AudioPlayerInterface()
: _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 {
return _mkPlayer.playlist.medias.isNotEmpty;
// if (mkSupportedPlatform) {

View File

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

View File

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