diff --git a/lib/models/playback/track_sources.dart b/lib/models/playback/track_sources.dart index c9d089a6..a08dca88 100644 --- a/lib/models/playback/track_sources.dart +++ b/lib/models/playback/track_sources.dart @@ -39,6 +39,8 @@ class TrackSourceQuery with _$TrackSourceQuery { /// Parses [SpotubeMedia]'s [uri] property to create a [TrackSourceQuery]. factory TrackSourceQuery.parseUri(String url) { + AppLogger.log.d("TrackSourceQuery parse $url"); + final isLocal = !url.startsWith("http"); if (isLocal) { diff --git a/lib/provider/server/routes/playback.dart b/lib/provider/server/routes/playback.dart index 25026425..e2b90220 100644 --- a/lib/provider/server/routes/playback.dart +++ b/lib/provider/server/routes/playback.dart @@ -215,16 +215,12 @@ class ServerPlaybackRoutes { ? activeSourcedTrack?.source : await ref.read( trackSourcesProvider( - TrackSourceQuery.parseUri(request.url.toString()), + //! Use [Request.requestedUri] as it contains full https url. + //! [Request.url] will exclude and starts relatively. (streams/... basically) + TrackSourceQuery.parseUri(request.requestedUri.toString()), ).future, ); - // This will be automatically updated by the notifier. - // if (playlist.activeTrack?.id == sourcedTrack?.query.id && - // sourcedTrack != null) { - // ref.read(activeTrackSourcesProvider.notifier).update(sourcedTrack); - // } - final (bytes: audioBytes, response: res) = await streamTrack(sourcedTrack!, request.headers); diff --git a/lib/services/sourced_track/sourced_track.dart b/lib/services/sourced_track/sourced_track.dart index 3e218bad..e4bc3636 100644 --- a/lib/services/sourced_track/sourced_track.dart +++ b/lib/services/sourced_track/sourced_track.dart @@ -84,6 +84,8 @@ abstract class SourcedTrack extends BasicSourcedTrack { onlyCleanArtist: true, ).trim(); + assert(title.trim().isNotEmpty, "Title should not be empty"); + return "$title - ${track.artists.join(", ")}"; } diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart index 0e494b89..e7899266 100644 --- a/lib/services/sourced_track/sources/youtube.dart +++ b/lib/services/sourced_track/sources/youtube.dart @@ -201,7 +201,10 @@ class YoutubeSourcedTrack extends SourcedTrack { final searchedVideos = await ref.read(youtubeEngineProvider).searchVideos(isrc.toString()); if (searchedVideos.isNotEmpty) { - isrcResults.addAll(searchedVideos + AppLogger.log + .d("${track.title} ISRC $isrc Total ${searchedVideos.length}"); + + final filteredMatches = searchedVideos .map(YoutubeVideoInfo.fromVideo) .map((YoutubeVideoInfo videoInfo) { final ytWords = videoInfo.title @@ -225,8 +228,16 @@ class YoutubeSourcedTrack extends SourcedTrack { } return null; }) - .whereType() - .toList()); + .nonNulls + .toList(); + + for (final match in filteredMatches) { + AppLogger.log.d( + "ISRC MATCH: ${match.id} ${match.title} by ${match.channelName} ${match.duration}", + ); + } + + isrcResults.addAll(filteredMatches); } } return isrcResults; @@ -247,7 +258,14 @@ class YoutubeSourcedTrack extends SourcedTrack { videoResults.addAll(isrcResults); if (isrcResults.isEmpty) { + AppLogger.log.w("No ISRC results found, falling back to SongLink"); + final links = await SongLinkService.links(query.id); + + for (final link in links) { + AppLogger.log.d("SongLink ${query.id} ${link.platform} ${link.url}"); + } + final ytLink = links.firstWhereOrNull( (link) => link.platform == "youtube", ); @@ -262,6 +280,8 @@ class YoutubeSourcedTrack extends SourcedTrack { // Ignore this error and continue with the search AppLogger.reportError(e, stack); } + } else { + AppLogger.log.w("No YouTube link found in SongLink results"); } } } diff --git a/lib/services/youtube_engine/youtube_explode_engine.dart b/lib/services/youtube_engine/youtube_explode_engine.dart index 461e01f6..26753e83 100644 --- a/lib/services/youtube_engine/youtube_explode_engine.dart +++ b/lib/services/youtube_engine/youtube_explode_engine.dart @@ -1,5 +1,8 @@ import 'dart:isolate'; +import 'package:dio/dio.dart'; +import 'package:spotube/services/dio/dio.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/youtube_engine/youtube_engine.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; @@ -152,27 +155,51 @@ class YouTubeExplodeEngine implements YouTubeEngine { ], ); - return StreamManifest( - streamManifest.audioOnly.map((stream) { - return AudioOnlyStreamInfo( - stream.videoId, - stream.tag, - stream.url, - stream.container, - stream.size, - stream.bitrate, - stream.audioCodec, - switch (stream.bitrate.bitsPerSecond) { - > 130 * 1024 => "high", - > 64 * 1024 => "medium", - _ => "low", + final accessibleStreams = []; + + for (final stream in streamManifest.audioOnly) { + // Call dio head request to check if the stream is accessible + final response = await globalDio.headUri( + stream.url, + options: Options( + followRedirects: true, + validateStatus: (status) { + return status != null && status < 500; }, - stream.fragments, - stream.codec, - stream.audioTrack, + ), + ); + + AppLogger.log.d( + "Stream $videoId Status ${response.statusCode} Codec ${stream.audioCodec} " + "Bitrate ${stream.bitrate} Container ${stream.container}", + ); + + if (response.statusCode != null && + response.statusCode! >= 200 && + response.statusCode! < 400) { + accessibleStreams.add( + AudioOnlyStreamInfo( + stream.videoId, + stream.tag, + stream.url, + stream.container, + stream.size, + stream.bitrate, + stream.audioCodec, + switch (stream.bitrate.bitsPerSecond) { + > 130 * 1024 => "high", + > 64 * 1024 => "medium", + _ => "low", + }, + stream.fragments, + stream.codec, + stream.audioTrack, + ), ); - }), - ); + } + } + + return StreamManifest(accessibleStreams); } @override