fix: alternative searched sources doesn't play #1059

This commit is contained in:
Kingkor Roy Tirtho 2024-01-22 19:02:10 +06:00
parent 59e0e6bb65
commit a8e9b824f3
8 changed files with 117 additions and 21 deletions

BIN
assets/jiosaavn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -34,6 +34,7 @@ class Assets {
AssetGenImage('assets/bengali-patterns-bg.jpg'); AssetGenImage('assets/bengali-patterns-bg.jpg');
static const AssetGenImage branding = AssetGenImage('assets/branding.png'); static const AssetGenImage branding = AssetGenImage('assets/branding.png');
static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png'); static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png');
static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png');
static const AssetGenImage likedTracks = static const AssetGenImage likedTracks =
AssetGenImage('assets/liked-tracks.jpg'); AssetGenImage('assets/liked-tracks.jpg');
static const AssetGenImage placeholder = static const AssetGenImage placeholder =
@ -76,6 +77,7 @@ class Assets {
bengaliPatternsBg, bengaliPatternsBg,
branding, branding,
emptyBox, emptyBox,
jiosaavn,
likedTracks, likedTracks,
placeholder, placeholder,
spotubeHeroBanner, spotubeHeroBanner,

View File

@ -109,4 +109,5 @@ abstract class SpotubeIcons {
static const normalize = FeatherIcons.barChart2; static const normalize = FeatherIcons.barChart2;
static const wikipedia = SimpleIcons.wikipedia; static const wikipedia = SimpleIcons.wikipedia;
static const discord = SimpleIcons.discord; static const discord = SimpleIcons.discord;
static const youtube = SimpleIcons.youtube;
} }

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart' hide Offset; import 'package:spotify/spotify.dart' hide Offset;
import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
@ -19,10 +20,28 @@ import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
import 'package:spotube/services/sourced_track/models/source_info.dart'; import 'package:spotube/services/sourced_track/models/source_info.dart';
import 'package:spotube/services/sourced_track/models/video_info.dart'; import 'package:spotube/services/sourced_track/models/video_info.dart';
import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart';
import 'package:spotube/services/sourced_track/sources/jiosaavn.dart';
import 'package:spotube/services/sourced_track/sources/piped.dart';
import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
final sourceInfoToIconMap = {
YoutubeSourceInfo: const Icon(SpotubeIcons.youtube, color: Color(0xFFFF0000)),
JioSaavnSourceInfo: Container(
height: 30,
width: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(90),
image: DecorationImage(
image: Assets.jiosaavn.provider(),
fit: BoxFit.cover,
),
),
),
PipedSourceInfo: const Icon(SpotubeIcons.piped),
};
class SiblingTracksSheet extends HookConsumerWidget { class SiblingTracksSheet extends HookConsumerWidget {
final bool floating; final bool floating;
const SiblingTracksSheet({ const SiblingTracksSheet({
@ -64,17 +83,34 @@ class SiblingTracksSheet extends HookConsumerWidget {
return <SourceInfo>[]; return <SourceInfo>[];
} }
final results = await youtubeClient.search.search(searchTerm.trim()); final resultsYt = await youtubeClient.search.search(searchTerm.trim());
final resultsJioSaavn =
await jiosaavnClient.search.songs(searchTerm.trim());
return await Future.wait( final searchResults = await Future.wait([
results.map(YoutubeVideoInfo.fromVideo).mapIndexed((i, video) async { ...resultsJioSaavn.results.mapIndexed((i, song) async {
final siblingType = JioSaavnSourcedTrack.toSiblingType(song);
return siblingType.info;
}),
...resultsYt
.map(YoutubeVideoInfo.fromVideo)
.mapIndexed((i, video) async {
final siblingType = await YoutubeSourcedTrack.toSiblingType(i, video); final siblingType = await YoutubeSourcedTrack.toSiblingType(i, video);
return siblingType.info; return siblingType.info;
}), }),
); ]);
final activeSourceInfo =
(playlist.activeTrack! as SourcedTrack).sourceInfo;
return searchResults
..removeWhere((element) => element.id == activeSourceInfo.id)
..insert(
0,
activeSourceInfo,
);
}, [ }, [
searchTerm, searchTerm,
searchMode.value, searchMode.value,
playlist.activeTrack,
]); ]);
final siblings = useMemoized( final siblings = useMemoized(
@ -104,6 +140,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
final itemBuilder = useCallback( final itemBuilder = useCallback(
(SourceInfo sourceInfo) { (SourceInfo sourceInfo) {
final icon = sourceInfoToIconMap[sourceInfo.runtimeType];
return ListTile( return ListTile(
title: Text(sourceInfo.title), title: Text(sourceInfo.title),
leading: Padding( leading: Padding(
@ -118,7 +155,12 @@ class SiblingTracksSheet extends HookConsumerWidget {
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
), ),
trailing: Text(sourceInfo.duration.toHumanReadableString()), trailing: Text(sourceInfo.duration.toHumanReadableString()),
subtitle: Text(sourceInfo.artist), subtitle: Row(
children: [
if (icon != null) icon,
Text("${sourceInfo.artist}"),
],
),
enabled: playlist.isFetching != true, enabled: playlist.isFetching != true,
selected: playlist.isFetching != true && selected: playlist.isFetching != true &&
sourceInfo.id == sourceInfo.id ==
@ -137,7 +179,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
[playlist.isFetching, playlist.activeTrack, siblings], [playlist.isFetching, playlist.activeTrack, siblings],
); );
var mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
return SafeArea( return SafeArea(
child: ClipRRect( child: ClipRRect(
borderRadius: borderRadius, borderRadius: borderRadius,

View File

@ -15,4 +15,4 @@ enum SourceQualities {
low, low,
} }
typedef SiblingType = ({SourceInfo info, SourceMap? source}); typedef SiblingType<T extends SourceInfo> = ({T info, SourceMap? source});

View File

@ -12,6 +12,19 @@ import 'package:spotube/extensions/string.dart';
final jiosaavnClient = JioSaavnClient(); final jiosaavnClient = JioSaavnClient();
class JioSaavnSourceInfo extends SourceInfo {
JioSaavnSourceInfo({
required super.id,
required super.title,
required super.artist,
required super.thumbnail,
required super.pageUrl,
required super.duration,
required super.artistUrl,
required super.album,
});
}
class JioSaavnSourcedTrack extends SourcedTrack { class JioSaavnSourcedTrack extends SourcedTrack {
JioSaavnSourcedTrack({ JioSaavnSourcedTrack({
required super.ref, required super.ref,
@ -70,7 +83,7 @@ class JioSaavnSourcedTrack extends SourcedTrack {
static SiblingType toSiblingType(SongResponse result) { static SiblingType toSiblingType(SongResponse result) {
final SiblingType sibling = ( final SiblingType sibling = (
info: SourceInfo( info: JioSaavnSourceInfo(
artist: [ artist: [
result.primaryArtists, result.primaryArtists,
if (result.featuredArtists.isNotEmpty) ", ", if (result.featuredArtists.isNotEmpty) ", ",
@ -155,12 +168,16 @@ class JioSaavnSourcedTrack extends SourcedTrack {
@override @override
Future<JioSaavnSourcedTrack?> swapWithSibling(SourceInfo sibling) async { Future<JioSaavnSourcedTrack?> swapWithSibling(SourceInfo sibling) async {
if (sibling.id == sourceInfo.id || if (sibling.id == sourceInfo.id) {
siblings.none((s) => s.id == sibling.id)) {
return null; return null;
} }
final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); // a sibling source that was fetched from the search results
final isStepSibling = siblings.none((s) => s.id == sibling.id);
final newSourceInfo = isStepSibling
? sibling
: siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList() final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, sourceInfo); ..insert(0, sourceInfo);

View File

@ -22,6 +22,19 @@ final pipedProvider = Provider<PipedClient>(
}, },
); );
class PipedSourceInfo extends SourceInfo {
PipedSourceInfo({
required super.id,
required super.title,
required super.artist,
required super.thumbnail,
required super.pageUrl,
required super.duration,
required super.artistUrl,
required super.album,
});
}
class PipedSourcedTrack extends SourcedTrack { class PipedSourcedTrack extends SourcedTrack {
PipedSourcedTrack({ PipedSourcedTrack({
required super.ref, required super.ref,
@ -71,7 +84,7 @@ class PipedSourcedTrack extends SourcedTrack {
ref: ref, ref: ref,
siblings: [], siblings: [],
source: toSourceMap(manifest), source: toSourceMap(manifest),
sourceInfo: SourceInfo( sourceInfo: PipedSourceInfo(
id: manifest.id, id: manifest.id,
artist: manifest.uploader, artist: manifest.uploader,
artistUrl: manifest.uploaderUrl, artistUrl: manifest.uploaderUrl,
@ -122,7 +135,7 @@ class PipedSourcedTrack extends SourcedTrack {
} }
final SiblingType sibling = ( final SiblingType sibling = (
info: SourceInfo( info: PipedSourceInfo(
id: item.id, id: item.id,
artist: item.channelName, artist: item.channelName,
artistUrl: "https://www.youtube.com/${item.channelId}", artistUrl: "https://www.youtube.com/${item.channelId}",
@ -233,12 +246,16 @@ class PipedSourcedTrack extends SourcedTrack {
@override @override
Future<SourcedTrack?> swapWithSibling(SourceInfo sibling) async { Future<SourcedTrack?> swapWithSibling(SourceInfo sibling) async {
if (sibling.id == sourceInfo.id || if (sibling.id == sourceInfo.id) {
siblings.none((s) => s.id == sibling.id)) {
return null; return null;
} }
final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); // a sibling source that was fetched from the search results
final isStepSibling = siblings.none((s) => s.id == sibling.id);
final newSourceInfo = isStepSibling
? sibling
: siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList() final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, sourceInfo); ..insert(0, sourceInfo);

View File

@ -17,6 +17,19 @@ final officialMusicRegex = RegExp(
caseSensitive: false, caseSensitive: false,
); );
class YoutubeSourceInfo extends SourceInfo {
YoutubeSourceInfo({
required super.id,
required super.title,
required super.artist,
required super.thumbnail,
required super.pageUrl,
required super.duration,
required super.artistUrl,
required super.album,
});
}
class YoutubeSourcedTrack extends SourcedTrack { class YoutubeSourcedTrack extends SourcedTrack {
YoutubeSourcedTrack({ YoutubeSourcedTrack({
required super.source, required super.source,
@ -64,7 +77,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
ref: ref, ref: ref,
siblings: [], siblings: [],
source: toSourceMap(manifest), source: toSourceMap(manifest),
sourceInfo: SourceInfo( sourceInfo: YoutubeSourceInfo(
id: item.id.value, id: item.id.value,
artist: item.author, artist: item.author,
artistUrl: "https://www.youtube.com/channel/${item.channelId}", artistUrl: "https://www.youtube.com/channel/${item.channelId}",
@ -117,7 +130,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
} }
final SiblingType sibling = ( final SiblingType sibling = (
info: SourceInfo( info: YoutubeSourceInfo(
id: item.id, id: item.id,
artist: item.channelName, artist: item.channelName,
artistUrl: "https://www.youtube.com/channel/${item.channelId}", artistUrl: "https://www.youtube.com/channel/${item.channelId}",
@ -217,12 +230,16 @@ class YoutubeSourcedTrack extends SourcedTrack {
@override @override
Future<YoutubeSourcedTrack?> swapWithSibling(SourceInfo sibling) async { Future<YoutubeSourcedTrack?> swapWithSibling(SourceInfo sibling) async {
if (sibling.id == sourceInfo.id || if (sibling.id == sourceInfo.id) {
siblings.none((s) => s.id == sibling.id)) {
return null; return null;
} }
final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); // a sibling source that was fetched from the search results
final isStepSibling = siblings.none((s) => s.id == sibling.id);
final newSourceInfo = isStepSibling
? sibling
: siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList() final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, sourceInfo); ..insert(0, sourceInfo);