mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00

* feat: add connect server support * feat: add ability discover and connect to same network Spotube(s) and sync queue * feat(connect): add player controls, shuffle, loop, progress bar and queue support * feat: make control page adaptive * feat: add volume control support * cd: upgrade macos runner version * chore: upgrade inappwebview version to 6 * feat: customized devices button * feat: add user icon next to devices button * feat: add play in remote device support * feat: show alert when new client connects * fix: ignore the device itself from broadcast list * fix: volume control not working * feat: add ability to select current device's output speaker
112 lines
3.1 KiB
Dart
112 lines
3.1 KiB
Dart
import 'package:bonsoir/bonsoir.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:spotube/services/device_info/device_info.dart';
|
|
|
|
class ConnectClientsState {
|
|
final List<BonsoirService> services;
|
|
final ResolvedBonsoirService? resolvedService;
|
|
final BonsoirDiscovery discovery;
|
|
|
|
ConnectClientsState({
|
|
required this.services,
|
|
required this.discovery,
|
|
this.resolvedService,
|
|
});
|
|
|
|
ConnectClientsState copyWith({
|
|
List<BonsoirService>? services,
|
|
BonsoirDiscovery? discovery,
|
|
ResolvedBonsoirService? resolvedService,
|
|
}) {
|
|
return ConnectClientsState(
|
|
services: services ?? this.services,
|
|
discovery: discovery ?? this.discovery,
|
|
resolvedService: resolvedService ?? this.resolvedService,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ConnectClientsNotifier extends AsyncNotifier<ConnectClientsState> {
|
|
ConnectClientsNotifier();
|
|
|
|
@override
|
|
build() async {
|
|
final discovery = BonsoirDiscovery(type: '_spotube._tcp');
|
|
final deviceId = await DeviceInfoService.instance.deviceId();
|
|
await discovery.ready;
|
|
|
|
final subscription = discovery.eventStream?.listen((event) {
|
|
// ignore device itself
|
|
if (event.service?.attributes["deviceId"] == deviceId) {
|
|
return;
|
|
}
|
|
|
|
switch (event.type) {
|
|
case BonsoirDiscoveryEventType.discoveryServiceFound:
|
|
state = AsyncData(state.value!.copyWith(
|
|
services: [
|
|
...?state.value?.services,
|
|
event.service!,
|
|
],
|
|
));
|
|
break;
|
|
case BonsoirDiscoveryEventType.discoveryServiceResolved:
|
|
state = AsyncData(
|
|
state.value!.copyWith(
|
|
resolvedService: event.service as ResolvedBonsoirService,
|
|
),
|
|
);
|
|
break;
|
|
case BonsoirDiscoveryEventType.discoveryServiceLost:
|
|
state = AsyncData(
|
|
ConnectClientsState(
|
|
services: state.value!.services
|
|
.where((s) => s.name != event.service!.name)
|
|
.toList(),
|
|
discovery: state.value!.discovery,
|
|
resolvedService:
|
|
event.service?.name == state.value!.resolvedService!.name
|
|
? null
|
|
: state.value!.resolvedService,
|
|
),
|
|
);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
|
|
ref.onDispose(() {
|
|
subscription?.cancel();
|
|
discovery.stop();
|
|
});
|
|
|
|
await discovery.start();
|
|
|
|
return ConnectClientsState(
|
|
services: [],
|
|
discovery: discovery,
|
|
);
|
|
}
|
|
|
|
Future<void> resolveService(BonsoirService service) async {
|
|
if (state.value == null) return;
|
|
await service.resolve(state.value!.discovery.serviceResolver);
|
|
}
|
|
|
|
Future<void> clearResolvedService() async {
|
|
if (state.value == null) return;
|
|
state = AsyncData(
|
|
ConnectClientsState(
|
|
services: state.value!.services,
|
|
discovery: state.value!.discovery,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
final connectClientsProvider =
|
|
AsyncNotifierProvider<ConnectClientsNotifier, ConnectClientsState>(
|
|
() => ConnectClientsNotifier(),
|
|
);
|