3 Commits

Author SHA1 Message Date
222d50d80d 🐛 Bug fixes of querying backend 2024-09-06 18:10:12 +08:00
499bca5b1c Better netease music check 2024-09-06 16:37:49 +08:00
252e4619f7 🐛 Fix player view layout issue 2024-09-06 16:22:39 +08:00
9 changed files with 260 additions and 218 deletions

View File

@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>

View File

@@ -82,7 +82,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
padding: const EdgeInsets.symmetric(vertical: 24), padding: const EdgeInsets.symmetric(vertical: 24),
children: [ children: [
Obx( Obx(
() => LimitedBox( () => Center(
child: LimitedBox(
maxHeight: maxAlbumSize, maxHeight: maxAlbumSize,
maxWidth: maxAlbumSize, maxWidth: maxAlbumSize,
child: Hero( child: Hero(
@@ -90,13 +91,15 @@ class _PlayerScreenState extends State<PlayerScreen> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: ClipRRect( child: ClipRRect(
borderRadius: borderRadius: const BorderRadius.all(
const BorderRadius.all(Radius.circular(16)), Radius.circular(16),
),
child: _albumArt != null child: _albumArt != null
? AutoCacheImage( ? AutoCacheImage(
_albumArt!, _albumArt!,
width: albumSize, width: albumSize,
height: albumSize, height: albumSize,
fit: BoxFit.cover,
) )
: Container( : Container(
color: Theme.of(context) color: Theme.of(context)
@@ -113,6 +116,7 @@ class _PlayerScreenState extends State<PlayerScreen> {
), ),
), ),
), ),
),
const Gap(24), const Gap(24),
Obx( Obx(
() => Row( () => Row(
@@ -309,7 +313,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
], ],
), ),
const Gap(20), const Gap(20),
SizedBox( Center(
child: SizedBox(
height: 40, height: 40,
child: ListView( child: ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
@@ -392,6 +397,7 @@ class _PlayerScreenState extends State<PlayerScreen> {
], ],
), ),
), ),
),
], ],
), ),
), ),

View File

@@ -109,8 +109,9 @@ abstract class SourcedTrack extends Track {
_ => await YoutubeSourcedTrack.fetchFromTrack(track: track), _ => await YoutubeSourcedTrack.fetchFromTrack(track: track),
}; };
} on TrackNotFoundError catch (err) { } on TrackNotFoundError catch (err) {
Get.find<ErrorNotifier>() Get.find<ErrorNotifier>().showError(
.showError('${err.toString()} via ${preferences.audioSource.label}'); '${err.toString()} via ${preferences.audioSource.label}, querying in fallback sources...',
);
return switch (preferences.audioSource) { return switch (preferences.audioSource) {
AudioSource.piped || AudioSource.piped ||
AudioSource.youtube => AudioSource.youtube =>

View File

@@ -93,6 +93,12 @@ class NeteaseSourcedTrack extends SourcedTrack {
throw TrackNotFoundError(track); throw TrackNotFoundError(track);
} }
final client = getClient();
final checkResp = await client.get(
'/check/music?id=${siblings.first.info.id}&realIP=${await lookupRealIp()}',
);
if (checkResp.body['success'] != true) throw TrackNotFoundError(track);
await db.database.into(db.database.sourceMatchTable).insert( await db.database.into(db.database.sourceMatchTable).insert(
SourceMatchTableCompanion.insert( SourceMatchTableCompanion.insert(
trackId: track.id!, trackId: track.id!,
@@ -111,8 +117,14 @@ class NeteaseSourcedTrack extends SourcedTrack {
final client = getClient(); final client = getClient();
final resp = await client.get('/song/detail?ids=${cachedSource.sourceId}'); final resp = await client.get('/song/detail?ids=${cachedSource.sourceId}');
print(resp.body); final item = (resp.body['songs'] as List<dynamic>).firstOrNull;
final item = resp.body['songs'][0];
if (item == null) throw TrackNotFoundError(track);
final checkResp = await client.get(
'/check/music?id=${item['id']}&realIP=${await lookupRealIp()}',
);
if (checkResp.body['success'] != true) throw TrackNotFoundError(track);
return NeteaseSourcedTrack( return NeteaseSourcedTrack(
siblings: [], siblings: [],
@@ -155,8 +167,11 @@ class NeteaseSourcedTrack extends SourcedTrack {
final query = SourcedTrack.getSearchTerm(track); final query = SourcedTrack.getSearchTerm(track);
final client = getClient(); final client = getClient();
final resp = final resp = await client.get(
await client.get('/search?keywords=${Uri.encodeComponent(query)}'); '/search?keywords=${Uri.encodeComponent(query)}&realIP=${NeteaseSourcedTrack.lookupRealIp()}',
);
if (resp.body?['code'] == 405) throw TrackNotFoundError(track);
print(resp.body);
final results = resp.body['result']['songs']; final results = resp.body['result']['songs'];
// We can just trust netease music for now // We can just trust netease music for now
@@ -202,7 +217,7 @@ class NeteaseSourcedTrack extends SourcedTrack {
final client = getClient(); final client = getClient();
final resp = await client.get('/song/detail?ids=${newSourceInfo.id}'); final resp = await client.get('/song/detail?ids=${newSourceInfo.id}');
final item = resp.body['songs'][0]; final item = (resp.body['songs'] as List<dynamic>).first;
final (:info, :source) = toSiblingType(item); final (:info, :source) = toSiblingType(item);
@@ -227,11 +242,10 @@ class NeteaseSourcedTrack extends SourcedTrack {
); );
} }
static SiblingType toSiblingType(dynamic item) { static NeteaseSourceInfo toSourceInfo(dynamic item) {
final firstArtist = item['ar'] != null ? item['ar'][0] : item['artists'][0]; final firstArtist = item['ar'] != null ? item['ar'][0] : item['artists'][0];
final SiblingType sibling = ( return NeteaseSourceInfo(
info: NeteaseSourceInfo(
id: item['id'].toString(), id: item['id'].toString(),
artist: item['ar'] != null artist: item['ar'] != null
? item['ar'].map((x) => x['name']).join(',') ? item['ar'].map((x) => x['name']).join(',')
@@ -245,7 +259,12 @@ class NeteaseSourcedTrack extends SourcedTrack {
? Duration(milliseconds: item['dt']) ? Duration(milliseconds: item['dt'])
: Duration(milliseconds: item['duration']), : Duration(milliseconds: item['duration']),
album: item['al']?['name'], album: item['al']?['name'],
), );
}
static SiblingType toSiblingType(dynamic item) {
final SiblingType sibling = (
info: toSourceInfo(item),
source: toSourceMap(item), source: toSourceMap(item),
); );

View File

@@ -69,6 +69,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
sourceId: siblings.first.info.id, sourceId: siblings.first.info.id,
sourceType: const Value(SourceType.youtube), sourceType: const Value(SourceType.youtube),
), ),
mode: InsertMode.insertOrReplace,
); );
return YoutubeSourcedTrack( return YoutubeSourcedTrack(

View File

@@ -5,8 +5,10 @@ import 'package:rhythm_box/platform.dart';
class AutoCacheImage extends StatelessWidget { class AutoCacheImage extends StatelessWidget {
final String url; final String url;
final double? width, height; final double? width, height;
final BoxFit? fit;
const AutoCacheImage(this.url, {super.key, this.width, this.height}); const AutoCacheImage(this.url,
{super.key, this.width, this.height, this.fit});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -15,12 +17,14 @@ class AutoCacheImage extends StatelessWidget {
imageUrl: url, imageUrl: url,
width: width, width: width,
height: height, height: height,
fit: fit,
); );
} }
return Image.network( return Image.network(
url, url,
width: width, width: width,
height: height, height: height,
fit: fit,
); );
} }

View File

@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:rhythm_box/providers/audio_player.dart'; import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/providers/user_preferences.dart'; import 'package:rhythm_box/providers/user_preferences.dart';
import 'package:rhythm_box/services/database/database.dart'; import 'package:rhythm_box/services/database/database.dart';
import 'package:rhythm_box/services/duration.dart'; import 'package:rhythm_box/services/duration.dart';
@@ -89,13 +90,17 @@ class _SiblingTracksState extends State<SiblingTracks> {
final preferences = Get.find<UserPreferencesProvider>().state.value; final preferences = Get.find<UserPreferencesProvider>().state.value;
final searchTerm = _searchTermController.text.trim(); final searchTerm = _searchTermController.text.trim();
try {
if (preferences.audioSource == AudioSource.youtube || if (preferences.audioSource == AudioSource.youtube ||
preferences.audioSource == AudioSource.piped) { preferences.audioSource == AudioSource.piped) {
final resultsYt = await youtubeClient.search.search(searchTerm.trim()); final resultsYt = await youtubeClient.search.search(searchTerm.trim());
final searchResults = await Future.wait( final searchResults = await Future.wait(
resultsYt.map(YoutubeVideoInfo.fromVideo).mapIndexed((i, video) async { resultsYt
final siblingType = await YoutubeSourcedTrack.toSiblingType(i, video); .map(YoutubeVideoInfo.fromVideo)
.mapIndexed((i, video) async {
final siblingType =
await YoutubeSourcedTrack.toSiblingType(i, video);
return siblingType.info; return siblingType.info;
}), }),
); );
@@ -111,11 +116,10 @@ class _SiblingTracksState extends State<SiblingTracks> {
); );
} else if (preferences.audioSource == AudioSource.netease) { } else if (preferences.audioSource == AudioSource.netease) {
final client = NeteaseSourcedTrack.getClient(); final client = NeteaseSourcedTrack.getClient();
final resp = await client final resp = await client.get(
.get('/search?keywords=${Uri.encodeComponent(searchTerm)}'); '/search?keywords=${Uri.encodeComponent(searchTerm)}&realIP=${NeteaseSourcedTrack.lookupRealIp()}');
final searchResults = resp.body['result']['songs'] final searchResults = resp.body['result']['songs']
.map(NeteaseSourcedTrack.toSiblingType) .map(NeteaseSourcedTrack.toSourceInfo)
.map((x) => x.info)
.toList(); .toList();
final activeSourceInfo = (_activeTrack! as SourcedTrack).sourceInfo; final activeSourceInfo = (_activeTrack! as SourcedTrack).sourceInfo;
@@ -129,9 +133,12 @@ class _SiblingTracksState extends State<SiblingTracks> {
growable: true, growable: true,
); );
} }
} catch (err) {
Get.find<ErrorNotifier>().showError(err.toString());
} finally {
setState(() => _isSearching = false); setState(() => _isSearching = false);
} }
}
@override @override
void initState() { void initState() {

View File

@@ -28,6 +28,8 @@
<string>MainMenu</string> <string>MainMenu</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.music</string> <string>public.app-category.music</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
</dict> </dict>

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+10 version: 1.0.0+11
environment: environment:
sdk: ^3.5.0 sdk: ^3.5.0