spotube/lib/provider/audio_player/audio_player_streams.dart
Alessio fece073def This pull request primarily involves the removal of several configuration files and assets, as well as minor updates to documentation. The most significant changes are the deletion of various .vscode configuration files and the removal of unused assets from the project.
Configuration File Removals:

    .vscode/c_cpp_properties.json: Removed the entire configuration for C/C++ properties.
    .vscode/launch.json: Removed the Dart launch configurations for different environments and modes.
    .vscode/settings.json: Removed settings related to CMake, spell checking, file nesting, and Dart Flutter SDK path.
    .vscode/snippets.code-snippets: Removed code snippets for Dart, including PaginatedState and PaginatedNotifier templates.
    .vscode/tasks.json: Removed the tasks configuration file.

Documentation Updates:

    CONTRIBUTION.md: Removed heart emoji from the introductory text.
    README.md: Updated the logo image and made minor text adjustments, including removing emojis and updating section titles. [1] [2] [3] [4] [5]

Asset Removals:

    lib/collections/assets.gen.dart: Removed multiple unused asset references, including images related to Spotube logos and banners. [1] [2] [3]

Minor Code Cleanups:

    cli/commands/build/linux.dart, cli/commands/build/windows.dart, cli/commands/translated.dart, cli/commands/untranslated.dart: Adjusted import statements for consistency. [1] [2] [3] [4]
    integration_test/app_test.dart: Removed an unnecessary blank line.
    lib/collections/routes.dart: Commented out the TrackRoute configuration.
2025-04-13 18:40:37 +02:00

143 lines
4.7 KiB
Dart

import 'dart:async';
import 'dart:math';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/audio_player/state.dart';
import 'package:spotube/provider/discord_provider.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/scrobbler/scrobbler.dart';
import 'package:spotube/provider/server/sourced_track.dart';
import 'package:spotube/provider/skip_segments/skip_segments.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/audio_services/audio_services.dart';
import 'package:spotube/services/logger/logger.dart';
class AudioPlayerStreamListeners {
final Ref ref;
late final AudioServices notificationService;
AudioPlayerStreamListeners(this.ref) {
AudioServices.create(ref, ref.read(audioPlayerProvider.notifier)).then(
(value) => notificationService = value,
);
final subscriptions = [
subscribeToPlaylist(),
subscribeToSkipSponsor(),
subscribeToScrobbleChanged(),
subscribeToPosition(),
subscribeToPlayerError(),
];
ref.onDispose(() {
for (final subscription in subscriptions) {
subscription.cancel();
}
});
}
ScrobblerNotifier get scrobbler => ref.read(scrobblerProvider.notifier);
UserPreferences get preferences => ref.read(userPreferencesProvider);
DiscordNotifier get discord => ref.read(discordProvider.notifier);
AudioPlayerState get audioPlayerState => ref.read(audioPlayerProvider);
PlaybackHistoryActions get history =>
ref.read(playbackHistoryActionsProvider);
StreamSubscription subscribeToPlaylist() {
return audioPlayer.playlistStream.listen((mpvPlaylist) {
try {
if (audioPlayerState.activeTrack == null) return;
notificationService.addTrack(audioPlayerState.activeTrack!);
discord.updatePresence(audioPlayerState.activeTrack!);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
}
StreamSubscription subscribeToSkipSponsor() {
return audioPlayer.positionStream.listen((position) async {
try {
final currentSegments = await ref.read(segmentProvider.future);
if (currentSegments?.segments.isNotEmpty != true ||
position < const Duration(seconds: 3)) return;
for (final segment in currentSegments!.segments) {
final seconds = position.inSeconds;
if (seconds < segment.start || seconds >= segment.end) continue;
await audioPlayer.seek(Duration(seconds: segment.end + 1));
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
}
StreamSubscription subscribeToScrobbleChanged() {
String? lastScrobbled;
return audioPlayer.positionStream.listen((position) {
try {
final uid = audioPlayerState.activeTrack is LocalTrack
? (audioPlayerState.activeTrack as LocalTrack).path
: audioPlayerState.activeTrack?.id;
if (audioPlayerState.activeTrack == null ||
lastScrobbled == uid ||
position.inSeconds < 30) {
return;
}
scrobbler.scrobble(audioPlayerState.activeTrack!);
history.addTrack(audioPlayerState.activeTrack!);
lastScrobbled = uid;
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
}
StreamSubscription subscribeToPosition() {
String lastTrack = ""; // used to prevent multiple calls to the same track
return audioPlayer.positionStream.listen((event) async {
final percentProgress =
(event.inSeconds / max(audioPlayer.duration.inSeconds, 1)) * 100;
try {
if (percentProgress < 80 ||
audioPlayerState.playlist.index == -1 ||
audioPlayerState.playlist.index ==
audioPlayerState.tracks.length - 1) {
return;
}
final nextTrack = SpotubeMedia.fromMedia(
audioPlayerState.playlist.medias
.elementAt(audioPlayerState.playlist.index + 1),
);
if (lastTrack == nextTrack.track.id || nextTrack.track is LocalTrack) {
return;
}
try {
await ref.read(sourcedTrackProvider(nextTrack).future);
} finally {
lastTrack = nextTrack.track.id!;
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
}
StreamSubscription subscribeToPlayerError() {
return audioPlayer.errorStream.listen((event) {});
}
}
final audioPlayerStreamListenersProvider =
Provider<AudioPlayerStreamListeners>(AudioPlayerStreamListeners.new);