Skip to content

Commit

Permalink
refactor: use tcp server based track matcher (#1386)
Browse files Browse the repository at this point in the history
* refactor: remove SourcedTrack based audio player and utilize mediakit playback system

* feat: implement local (loopback) server to resolve stream source and leverage the media_kit playback API

* feat: add source change support and re-add prefetching tracks

* fix: assign lastId when track fetch completes regardless of error

* chore: remove print statements

* fix: remote queue not working

* fix: increase mpv network timeout to reduce auto-skipping

* fix: do not pre-fetch local tracks

* fix(proxy-playlist): reset collections on load

* chore: fix lint warnings

* fix(mobile): player overlay should not be visible when the player is not playing

* chore: fix typo in turkish translation

* cd: checkout PR branch

* cd: upgrade flutter version

* chore: fix lint errors
  • Loading branch information
KRTirtho authored Apr 11, 2024
1 parent b948872 commit 22a49e5
Show file tree
Hide file tree
Showing 55 changed files with 591 additions and 1,839 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/pr-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ on:
pull_request:

env:
FLUTTER_VERSION: '3.16.0'
FLUTTER_VERSION: '3.19.5'

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
"cmake.configureOnOpen": false,
"cSpell.words": [
"acousticness",
"ambiguate",
"Amoled",
"Buildless",
"danceability",
"fuzzywuzzy",
"gapless",
"instrumentalness",
"Mpris",
"RGBO",
"riverpod",
"Scrobblenaut",
"skeletonizer",
"songlink",
"speechiness",
"Spotube",
"winget"
Expand Down
8 changes: 5 additions & 3 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ linter:
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
analyzer:
enable-experiment:
- records
- patterns
errors:
invalid_annotation_target: ignore
plugins:
- custom_lint
exclude:
- "**.freezed.dart"
- "**.g.dart"
- "**.gr.dart"
- "**/generated_plugin_registrant.dart"
2 changes: 2 additions & 0 deletions bin/translated_messages.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: avoid_print

import 'dart:convert';
import 'dart:io';

Expand Down
3 changes: 2 additions & 1 deletion bin/untranslated_messages.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: avoid_print

import 'dart:convert';
import 'dart:io';

Expand Down Expand Up @@ -40,7 +42,6 @@ void main(List<String> args) {
"Translate following to their appropriate locale for flutter arb translations files."
" Put the respective new translations in a map of their corresponding locale.",
);
// ignore: avoid_print
print(
const JsonEncoder.withIndent(' ').convert(
args.isNotEmpty ? messagesWithValues[args.first] : messagesWithValues,
Expand Down
2 changes: 1 addition & 1 deletion lib/components/album/album_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class AlbumCard extends HookConsumerWidget {

final fetchedTracks = await fetchAllTrack();

if (fetchedTracks.isEmpty) return;
if (fetchedTracks.isEmpty || !context.mounted) return;

final isRemoteDevice = await showSelectDeviceDialog(context, ref);
if (isRemoteDevice) {
Expand Down
4 changes: 1 addition & 3 deletions lib/components/library/user_local_tracks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/service_utils.dart';
// ignore: depend_on_referenced_packages
import 'package:flutter_rust_bridge/flutter_rust_bridge.dart' show FfiException;

const supportedAudioTypes = [
Expand Down Expand Up @@ -185,9 +186,6 @@ class UserLocalTracks extends HookConsumerWidget {
ref,
trackSnapshot.asData!.value,
);
} else {
// TODO: Remove stop capability
// playlistNotifier.stop();
}
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/components/player/player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class PlayerView extends HookConsumerWidget {

final topPadding = MediaQueryData.fromView(View.of(context)).padding.top;

// ignore: deprecated_member_use
return WillPopScope(
onWillPop: () async {
await panelController.close();
Expand Down
5 changes: 2 additions & 3 deletions lib/components/player/player_overlay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@ class PlayerOverlay extends HookConsumerWidget {

@override
Widget build(BuildContext context, ref) {
final canShow = ref.watch(
ProxyPlaylistNotifier.provider.select((s) => s.active != null),
);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final canShow = playlist.activeTrack != null;

final playing =
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;

Expand Down
13 changes: 5 additions & 8 deletions lib/components/player/player_queue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ class PlayerQueue extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final mediaQuery = MediaQuery.of(context);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final playlist = ref.watch(ProxyPlaylistNotifier.provider);

final controller = useAutoScrollController();
final searchText = useState('');

Expand Down Expand Up @@ -161,7 +160,7 @@ class PlayerQueue extends HookConsumerWidget {
snap: false,
backgroundColor: Colors.transparent,
elevation: 0,
automaticallyImplyLeading: !isSearching.value,
automaticallyImplyLeading: false,
title: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 10,
Expand Down Expand Up @@ -241,7 +240,7 @@ class PlayerQueue extends HookConsumerWidget {
],
),
onPressed: () {
playlistNotifier.stop();
onStop();
Navigator.of(context).pop();
},
),
Expand All @@ -251,9 +250,7 @@ class PlayerQueue extends HookConsumerWidget {
),
const SliverGap(10),
SliverReorderableList(
onReorder: (oldIndex, newIndex) {
playlistNotifier.moveTrack(oldIndex, newIndex);
},
onReorder: onReorder,
itemCount: filteredTracks.length,
onReorderStart: (index) {
HapticFeedback.selectionClick();
Expand All @@ -277,7 +274,7 @@ class PlayerQueue extends HookConsumerWidget {
if (playlist.activeTrack?.id == track.id) {
return;
}
await playlistNotifier.jumpToTrack(track);
await onJump(track);
},
leadingActions: [
if (!isSearching.value &&
Expand Down
44 changes: 20 additions & 24 deletions lib/components/player/sibling_tracks_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart' hide Offset;
import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/spotube_icons.dart';

Expand All @@ -16,6 +15,7 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/duration.dart';
import 'package:spotube/hooks/utils/use_debounce.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/server/active_sourced_track.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
import 'package:spotube/services/sourced_track/models/source_info.dart';
Expand Down Expand Up @@ -53,21 +53,22 @@ class SiblingTracksSheet extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final theme = Theme.of(context);
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final preferences = ref.watch(userPreferencesProvider);

final isSearching = useState(false);
final searchMode = useState(preferences.searchMode);
final activeTrackNotifier = ref.watch(activeSourcedTrackProvider.notifier);
final activeTrack =
ref.watch(activeSourcedTrackProvider) ?? playlist.activeTrack;

final title = ServiceUtils.getTitle(
playlist.activeTrack?.name ?? "",
artists:
playlist.activeTrack?.artists?.map((e) => e.name!).toList() ?? [],
activeTrack?.name ?? "",
artists: activeTrack?.artists?.map((e) => e.name!).toList() ?? [],
onlyCleanArtist: true,
).trim();

final defaultSearchTerm =
"$title - ${playlist.activeTrack?.artists?.asString() ?? ""}";
"$title - ${activeTrack?.artists?.asString() ?? ""}";
final searchController = useTextEditingController(
text: defaultSearchTerm,
);
Expand All @@ -91,8 +92,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
return siblingType.info;
}));

final activeSourceInfo =
(playlist.activeTrack! as SourcedTrack).sourceInfo;
final activeSourceInfo = (activeTrack! as SourcedTrack).sourceInfo;

return results
..removeWhere((element) => element.id == activeSourceInfo.id)
Expand All @@ -112,8 +112,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
return siblingType.info;
}),
);
final activeSourceInfo =
(playlist.activeTrack! as SourcedTrack).sourceInfo;
final activeSourceInfo = (activeTrack! as SourcedTrack).sourceInfo;
return searchResults
..removeWhere((element) => element.id == activeSourceInfo.id)
..insert(
Expand All @@ -124,18 +123,18 @@ class SiblingTracksSheet extends HookConsumerWidget {
}, [
searchTerm,
searchMode.value,
playlist.activeTrack,
activeTrack,
preferences.audioSource,
]);

final siblings = useMemoized(
() => playlist.isFetching == false
? [
(playlist.activeTrack as SourcedTrack).sourceInfo,
...(playlist.activeTrack as SourcedTrack).siblings,
(activeTrack as SourcedTrack).sourceInfo,
...activeTrack.siblings,
]
: <SourceInfo>[],
[playlist.isFetching, playlist.activeTrack],
[playlist.isFetching, activeTrack],
);

final borderRadius = floating
Expand All @@ -146,12 +145,11 @@ class SiblingTracksSheet extends HookConsumerWidget {
);

useEffect(() {
if (playlist.activeTrack is SourcedTrack &&
(playlist.activeTrack as SourcedTrack).siblings.isEmpty) {
playlistNotifier.populateSibling();
if (activeTrack is SourcedTrack && activeTrack.siblings.isEmpty) {
activeTrackNotifier.populateSibling();
}
return null;
}, [playlist.activeTrack]);
}, [activeTrack]);

final itemBuilder = useCallback(
(SourceInfo sourceInfo) {
Expand All @@ -178,20 +176,18 @@ class SiblingTracksSheet extends HookConsumerWidget {
),
enabled: playlist.isFetching != true,
selected: playlist.isFetching != true &&
sourceInfo.id ==
(playlist.activeTrack as SourcedTrack).sourceInfo.id,
sourceInfo.id == (activeTrack as SourcedTrack).sourceInfo.id,
selectedTileColor: theme.popupMenuTheme.color,
onTap: () {
if (playlist.isFetching == false &&
sourceInfo.id !=
(playlist.activeTrack as SourcedTrack).sourceInfo.id) {
playlistNotifier.swapSibling(sourceInfo);
sourceInfo.id != (activeTrack as SourcedTrack).sourceInfo.id) {
activeTrackNotifier.swapSibling(sourceInfo);
Navigator.of(context).pop();
}
},
);
},
[playlist.isFetching, playlist.activeTrack, siblings],
[playlist.isFetching, activeTrack, siblings],
);

final mediaQuery = MediaQuery.of(context);
Expand Down
2 changes: 1 addition & 1 deletion lib/components/playlist/playlist_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class PlaylistCard extends HookConsumerWidget {

List<Track> fetchedTracks = await fetchAllTracks();

if (fetchedTracks.isEmpty) return;
if (fetchedTracks.isEmpty || !context.mounted) return;

final isRemoteDevice = await showSelectDeviceDialog(context, ref);
if (isRemoteDevice) {
Expand Down
2 changes: 0 additions & 2 deletions lib/components/root/bottom_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import 'package:spotube/hooks/utils/use_brightness_value.dart';
import 'package:spotube/models/logger.dart';
import 'package:flutter/material.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/connect/connect.dart' hide volumeProvider;
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
Expand All @@ -36,7 +35,6 @@ class BottomPlayer extends HookConsumerWidget {
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final layoutMode =
ref.watch(userPreferencesProvider.select((s) => s.layoutMode));
final remoteControl = ref.watch(connectProvider);

final mediaQuery = MediaQuery.of(context);

Expand Down
2 changes: 1 addition & 1 deletion lib/components/shared/bordered_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class BorderedText extends StatelessWidget {
strutStyle: child.strutStyle,
textAlign: child.textAlign,
textDirection: child.textDirection,
textScaleFactor: child.textScaleFactor,
textScaler: child.textScaler,
),
child,
],
Expand Down
1 change: 1 addition & 0 deletions lib/components/shared/page_window_title_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ class MouseStateBuilder extends StatefulWidget {
final VoidCallback? onPressed;
const MouseStateBuilder({super.key, required this.builder, this.onPressed});
@override
// ignore: library_private_types_in_public_api
_MouseStateBuilderState createState() => _MouseStateBuilderState();
}

Expand Down
2 changes: 1 addition & 1 deletion lib/components/shared/panels/controller.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
part of panels;
part of './sliding_up_panel.dart';

class PanelController extends ChangeNotifier {
SlidingUpPanelState? _panelState;
Expand Down
2 changes: 1 addition & 1 deletion lib/components/shared/panels/helpers.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
part of panels;
part of "./sliding_up_panel.dart";

/// if you want to prevent the panel from being dragged using the widget,
/// wrap the widget with this
Expand Down
2 changes: 1 addition & 1 deletion lib/components/shared/track_tile/track_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class TrackTile extends HookConsumerWidget {
Expanded(
flex: 4,
child: switch (track.runtimeType) {
LocalTrack => Text(
LocalTrack() => Text(
track.album!.name!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class TrackViewHeaderButtons extends HookConsumerWidget {

final allTracks = await props.pagination.onFetchAll();

if (!context.mounted) return;

final isRemoteDevice = await showSelectDeviceDialog(context, ref);
if (isRemoteDevice) {
final remotePlayback = ref.read(connectProvider.notifier);
Expand Down Expand Up @@ -76,6 +78,8 @@ class TrackViewHeaderButtons extends HookConsumerWidget {

final allTracks = await props.pagination.onFetchAll();

if (!context.mounted) return;

final isRemoteDevice = await showSelectDeviceDialog(context, ref);
if (isRemoteDevice) {
final remotePlayback = ref.read(connectProvider.notifier);
Expand Down
7 changes: 7 additions & 0 deletions lib/extensions/track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:path/path.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/extensions/album_simple.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/services/audio_player/audio_player.dart';

extension TrackExtensions on Track {
Track fromFile(
Expand Down Expand Up @@ -90,3 +91,9 @@ extension TrackSimpleExtensions on TrackSimple {
return track;
}
}

extension TracksToMediaExtension on Iterable<Track> {
List<SpotubeMedia> asMediaList() {
return map((track) => SpotubeMedia(track)).toList();
}
}
Loading

0 comments on commit 22a49e5

Please sign in to comment.