diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md
index 13996cea..e859f9e6 100644
--- a/CONTRIBUTION.md
+++ b/CONTRIBUTION.md
@@ -25,7 +25,7 @@ All types of contributions are encouraged and valued. See the [Table of Contents
- [Before Submitting an Enhancement](#before-submitting-an-enhancement)
- [How Do I Submit a Good Enhancement Suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
- [Your First Code Contribution](#your-first-code-contribution)
- - [Submit translations](#submit-translations)
+ - [Submit Translations](#submit-translations)
## Code of Conduct
@@ -123,16 +123,16 @@ Do the following:
- Install Development dependencies in linux
- Debian (>=12/Bookworm)/Ubuntu
```bash
- $ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev
+ $ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev avahi-daemon avahi-discover avahi-utils libnss-mdns mdns-scan
```
- Use `libjsoncpp1` instead of `libjsoncpp25` (for Ubuntu < 22.04)
- Arch/Manjaro
```bash
- yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify
+ yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify avahi nss-mdns mdns-scan
```
- Fedora
```bash
- dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel
+ dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel avahi mdns-scan nss-mdns
```
- Clone the Repo
- Create a `.env` in root of the project following the `.env.example` template
diff --git a/ios/Podfile b/ios/Podfile
index bc3dcaa6..7235f482 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
+platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 8e103cfa..ffd511a4 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -66,5 +66,11 @@
UIViewControllerBasedStatusBarAppearance
+ NSLocalNetworkUsageDescription
+ To allow other devices on the network control playback of Spotube securely.
+ NSBonjourServices
+
+ _spotube._tcp
+
\ No newline at end of file
diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart
index 98c8ad45..8d497388 100644
--- a/lib/collections/spotube_icons.dart
+++ b/lib/collections/spotube_icons.dart
@@ -116,4 +116,5 @@ abstract class SpotubeIcons {
static const openCollective = SimpleIcons.opencollective;
static const anonymous = FeatherIcons.user;
static const history = FeatherIcons.clock;
+ static const connect = FeatherIcons.link;
}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 8257eac9..9fdcbc1b 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -313,5 +313,7 @@
"help_project_grow_description": "Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.",
"contribute_on_github": "Contribute on GitHub",
"donate_on_open_collective": "Donate on Open Collective",
- "browse_anonymously": "Browse Anonymously"
+ "browse_anonymously": "Browse Anonymously",
+ "enable_connect": "Enable Connect",
+ "enable_connect_description": "Control Spotube from other devices"
}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index 5c100fd3..65e21d51 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -23,6 +23,7 @@ import 'package:spotube/l10n/l10n.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/models/skip_segment.dart';
import 'package:spotube/models/source_match.dart';
+import 'package:spotube/provider/connect/server.dart';
import 'package:spotube/provider/palette_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
@@ -180,6 +181,8 @@ class SpotubeState extends ConsumerState {
ref.watch(paletteProvider.select((s) => s?.dominantColor?.color));
final router = ref.watch(routerProvider);
+ ref.read(connectServerProvider);
+
useDisableBatteryOptimizations();
useInitSysTray(ref);
useDeepLinking(ref);
diff --git a/lib/models/connect/connect.dart b/lib/models/connect/connect.dart
new file mode 100644
index 00000000..4d6920ac
--- /dev/null
+++ b/lib/models/connect/connect.dart
@@ -0,0 +1,15 @@
+library connect;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:spotify/spotify.dart';
+import 'package:spotube/extensions/track.dart';
+import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
+
+part 'connect.freezed.dart';
+part 'connect.g.dart';
+
+part 'ws_event.dart';
+part 'load.dart';
diff --git a/lib/models/connect/connect.freezed.dart b/lib/models/connect/connect.freezed.dart
new file mode 100644
index 00000000..dcbd783d
--- /dev/null
+++ b/lib/models/connect/connect.freezed.dart
@@ -0,0 +1,216 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'connect.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+WebSocketLoadEventData _$WebSocketLoadEventDataFromJson(
+ Map json) {
+ return _WebSocketLoadEventData.fromJson(json);
+}
+
+/// @nodoc
+mixin _$WebSocketLoadEventData {
+ @JsonKey(name: 'tracks', toJson: _tracksJson)
+ List