mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
fix: always fetching SponsorBlock if no segments found & download failing
This commit is contained in:
parent
33c7b64f06
commit
6ced0a0fad
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
|
||||||
class ConfirmDownloadDialog extends StatelessWidget {
|
class ConfirmDownloadDialog extends StatelessWidget {
|
||||||
@ -24,8 +25,9 @@ class ConfirmDownloadDialog extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content: Padding(
|
content: Container(
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
|
constraints: BoxConstraints(maxWidth: Breakpoints.sm),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -87,7 +89,7 @@ class BulletPoint extends StatelessWidget {
|
|||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text("●"),
|
const Text("\u2022"),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Flexible(child: Text(text)),
|
Flexible(child: Text(text)),
|
||||||
],
|
],
|
||||||
|
@ -20,6 +20,7 @@ import 'package:spotube/extensions/context.dart';
|
|||||||
import 'package:spotube/provider/download_manager_provider.dart';
|
import 'package:spotube/provider/download_manager_provider.dart';
|
||||||
import 'package:spotube/provider/blacklist_provider.dart';
|
import 'package:spotube/provider/blacklist_provider.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
||||||
final trackCollectionSortState =
|
final trackCollectionSortState =
|
||||||
@ -55,7 +56,9 @@ class TracksTableView extends HookConsumerWidget {
|
|||||||
final playback = ref.watch(ProxyPlaylistNotifier.notifier);
|
final playback = ref.watch(ProxyPlaylistNotifier.notifier);
|
||||||
ref.watch(downloadManagerProvider);
|
ref.watch(downloadManagerProvider);
|
||||||
final downloader = ref.watch(downloadManagerProvider.notifier);
|
final downloader = ref.watch(downloadManagerProvider.notifier);
|
||||||
TextStyle tableHeadStyle =
|
final apiType =
|
||||||
|
ref.watch(userPreferencesProvider.select((s) => s.youtubeApiType));
|
||||||
|
final tableHeadStyle =
|
||||||
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
|
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
|
||||||
|
|
||||||
final selected = useState<List<String>>([]);
|
final selected = useState<List<String>>([]);
|
||||||
@ -188,7 +191,8 @@ class TracksTableView extends HookConsumerWidget {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case "download":
|
case "download":
|
||||||
{
|
{
|
||||||
final confirmed = await showDialog(
|
final confirmed = apiType == YoutubeApiType.piped ||
|
||||||
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return const ConfirmDownloadDialog();
|
return const ConfirmDownloadDialog();
|
||||||
|
@ -14,6 +14,12 @@ final officialMusicRegex = RegExp(
|
|||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
class TrackNotFoundException implements Exception {
|
||||||
|
factory TrackNotFoundException(Track track) {
|
||||||
|
throw Exception("Failed to find any results for ${track.name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SpotubeTrack extends Track {
|
class SpotubeTrack extends Track {
|
||||||
final YoutubeVideoInfo ytTrack;
|
final YoutubeVideoInfo ytTrack;
|
||||||
final String ytUri;
|
final String ytUri;
|
||||||
@ -157,7 +163,7 @@ class SpotubeTrack extends Track {
|
|||||||
} else {
|
} else {
|
||||||
siblings = await fetchSiblings(track, client);
|
siblings = await fetchSiblings(track, client);
|
||||||
if (siblings.isEmpty) {
|
if (siblings.isEmpty) {
|
||||||
throw Exception("Failed to find any results for ${track.name}");
|
throw TrackNotFoundException(track);
|
||||||
}
|
}
|
||||||
(ytVideo, ytStreamUrl) =
|
(ytVideo, ytStreamUrl) =
|
||||||
await client.video(siblings.first.id, siblings.first.searchMode);
|
await client.video(siblings.first.id, siblings.first.searchMode);
|
||||||
|
@ -85,7 +85,7 @@ class DownloadManagerProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
final Ref<DownloadManagerProvider> ref;
|
final Ref<DownloadManagerProvider> ref;
|
||||||
|
|
||||||
YoutubeEndpoints get yt => ref.read(downloadYoutubeProvider);
|
YoutubeEndpoints get yt => ref.read(youtubeProvider);
|
||||||
String get downloadDirectory =>
|
String get downloadDirectory =>
|
||||||
ref.read(userPreferencesProvider.select((s) => s.downloadLocation));
|
ref.read(userPreferencesProvider.select((s) => s.downloadLocation));
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:catcher/catcher.dart';
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
@ -68,7 +69,12 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
() async {
|
() async {
|
||||||
notificationService = await AudioServices.create(ref, this);
|
notificationService = await AudioServices.create(ref, this);
|
||||||
|
|
||||||
({String source, List<SkipSegment> segments})? currentSegments;
|
// listeners state
|
||||||
|
final currentSegments =
|
||||||
|
// using source as unique id because alternative track source support
|
||||||
|
ObjectRef<({String source, List<SkipSegment> segments})?>(null);
|
||||||
|
final isPreSearching = ObjectRef(false);
|
||||||
|
final isFetchingSegments = ObjectRef(false);
|
||||||
|
|
||||||
audioPlayer.activeSourceChangedStream.listen((newActiveSource) async {
|
audioPlayer.activeSourceChangedStream.listen((newActiveSource) async {
|
||||||
try {
|
try {
|
||||||
@ -112,16 +118,14 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
bool isPreSearching = false;
|
|
||||||
|
|
||||||
listenTo2Percent(int percent) async {
|
listenTo2Percent(int percent) async {
|
||||||
if (isPreSearching ||
|
if (isPreSearching.value ||
|
||||||
audioPlayer.currentSource == null ||
|
audioPlayer.currentSource == null ||
|
||||||
audioPlayer.nextSource == null ||
|
audioPlayer.nextSource == null ||
|
||||||
isPlayable(audioPlayer.nextSource!)) return;
|
isPlayable(audioPlayer.nextSource!)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isPreSearching = true;
|
isPreSearching.value = true;
|
||||||
|
|
||||||
final oldTrack =
|
final oldTrack =
|
||||||
mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull;
|
mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull;
|
||||||
@ -138,51 +142,64 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
// Removing tracks that were not found to avoid queue interruption
|
||||||
|
// TODO: Add a flag to enable/disable skip not found tracks
|
||||||
|
if (e is TrackNotFoundException) {
|
||||||
|
final oldTrack =
|
||||||
|
mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull;
|
||||||
|
await removeTrack(oldTrack!.id!);
|
||||||
|
}
|
||||||
Catcher.reportCheckedError(e, stackTrace);
|
Catcher.reportCheckedError(e, stackTrace);
|
||||||
} finally {
|
} finally {
|
||||||
isPreSearching = false;
|
isPreSearching.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audioPlayer.percentCompletedStream(2).listen(listenTo2Percent);
|
audioPlayer.percentCompletedStream(2).listen(listenTo2Percent);
|
||||||
|
|
||||||
bool isFetchingSegments = false;
|
|
||||||
|
|
||||||
audioPlayer.positionStream.listen((position) async {
|
audioPlayer.positionStream.listen((position) async {
|
||||||
try {
|
|
||||||
if (state.activeTrack == null || state.activeTrack is LocalTrack) {
|
if (state.activeTrack == null || state.activeTrack is LocalTrack) {
|
||||||
isFetchingSegments = false;
|
isFetchingSegments.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// skipping in very first second breaks stream
|
|
||||||
if ((preferences.youtubeApiType == YoutubeApiType.piped &&
|
|
||||||
preferences.searchMode == SearchMode.youtubeMusic) ||
|
|
||||||
!preferences.skipNonMusic) return;
|
|
||||||
|
|
||||||
final notSameSegmentId =
|
|
||||||
currentSegments?.source != audioPlayer.currentSource;
|
|
||||||
|
|
||||||
if (currentSegments == null ||
|
|
||||||
(notSameSegmentId && !isFetchingSegments)) {
|
|
||||||
isFetchingSegments = true;
|
|
||||||
try {
|
try {
|
||||||
currentSegments = (
|
final isYTMusicMode =
|
||||||
|
preferences.youtubeApiType == YoutubeApiType.piped &&
|
||||||
|
preferences.searchMode == SearchMode.youtubeMusic;
|
||||||
|
|
||||||
|
if (isYTMusicMode || !preferences.skipNonMusic) return;
|
||||||
|
|
||||||
|
final isNotSameSegmentId =
|
||||||
|
currentSegments.value?.source != audioPlayer.currentSource;
|
||||||
|
|
||||||
|
if (currentSegments.value == null ||
|
||||||
|
(isNotSameSegmentId && !isFetchingSegments.value)) {
|
||||||
|
isFetchingSegments.value = true;
|
||||||
|
try {
|
||||||
|
currentSegments.value = (
|
||||||
source: audioPlayer.currentSource!,
|
source: audioPlayer.currentSource!,
|
||||||
segments: await getAndCacheSkipSegments(
|
segments: await getAndCacheSkipSegments(
|
||||||
(state.activeTrack as SpotubeTrack).ytTrack.id,
|
(state.activeTrack as SpotubeTrack).ytTrack.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
currentSegments.value = (
|
||||||
|
source: audioPlayer.currentSource!,
|
||||||
|
segments: [],
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isFetchingSegments = false;
|
isFetchingSegments.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final (source: _, :segments) = currentSegments!;
|
final (source: _, :segments) = currentSegments.value!;
|
||||||
|
|
||||||
|
// skipping in first 2 second breaks stream
|
||||||
if (segments.isEmpty || position < const Duration(seconds: 3)) return;
|
if (segments.isEmpty || position < const Duration(seconds: 3)) return;
|
||||||
|
|
||||||
for (final segment in segments) {
|
for (final segment in segments) {
|
||||||
if ((position.inSeconds >= segment.start &&
|
if (position.inSeconds >= segment.start &&
|
||||||
position.inSeconds < segment.end)) {
|
position.inSeconds < segment.end) {
|
||||||
await audioPlayer.seek(Duration(seconds: segment.end));
|
await audioPlayer.seek(Duration(seconds: segment.end));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,3 @@ final youtubeProvider = Provider<YoutubeEndpoints>((ref) {
|
|||||||
final preferences = ref.watch(userPreferencesProvider);
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
return YoutubeEndpoints(preferences);
|
return YoutubeEndpoints(preferences);
|
||||||
});
|
});
|
||||||
|
|
||||||
// this provider overrides the API provider to use piped.video for downloading
|
|
||||||
final downloadYoutubeProvider = Provider<YoutubeEndpoints>((ref) {
|
|
||||||
final preferences = ref.watch(userPreferencesProvider);
|
|
||||||
return YoutubeEndpoints(
|
|
||||||
preferences.copyWith(
|
|
||||||
youtubeApiType: YoutubeApiType.piped,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:smtc_windows/smtc_windows.dart'
|
import 'package:smtc_windows/smtc_windows.dart';
|
||||||
if (dart.library.html) 'package:spotube/services/audio_services/smtc_windows_web.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
Loading…
Reference in New Issue
Block a user