Skip to content

Commit

Permalink
feat(lyrics): add LRCLIB lyrics provider as fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Apr 4, 2024
1 parent c8dd802 commit 5afe823
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 110 deletions.
14 changes: 14 additions & 0 deletions lib/models/lyrics.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import 'package:lrc/lrc.dart';

class SubtitleSimple {
Uri uri;
String name;
List<LyricSlice> lyrics;
int rating;
String provider;

SubtitleSimple({
required this.uri,
required this.name,
required this.lyrics,
required this.rating,
required this.provider,
});

factory SubtitleSimple.fromJson(Map<String, dynamic> json) {
Expand All @@ -18,6 +23,7 @@ class SubtitleSimple {
.map((e) => LyricSlice.fromJson(e as Map<String, dynamic>))
.toList(),
rating: json["rating"] as int,
provider: json["provider"] as String? ?? "unknown",
);
}

Expand All @@ -27,6 +33,7 @@ class SubtitleSimple {
"name": name,
"lyrics": lyrics.map((e) => e.toJson()).toList(),
"rating": rating,
"provider": provider,
};
}
}
Expand All @@ -37,6 +44,13 @@ class LyricSlice {

LyricSlice({required this.time, required this.text});

factory LyricSlice.fromLrcLine(LrcLine line) {
return LyricSlice(
time: line.timestamp,
text: line.lyrics.trim(),
);
}

factory LyricSlice.fromJson(Map<String, dynamic> json) {
return LyricSlice(
time: Duration(milliseconds: json["time"]),
Expand Down
32 changes: 31 additions & 1 deletion lib/pages/lyrics/lyrics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import 'package:spotube/collections/spotube_icons.dart';
Expand All @@ -19,6 +20,7 @@ import 'package:spotube/pages/lyrics/synced_lyrics.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/provider/spotify/spotify.dart';

class LyricsPage extends HookConsumerWidget {
final bool isModal;
Expand All @@ -43,13 +45,41 @@ class LyricsPage extends HookConsumerWidget {
noSetBGColor: true,
);

final tabbar = ThemedButtonsTabBar(
PreferredSizeWidget tabbar = ThemedButtonsTabBar(
tabs: [
Tab(text: " ${context.l10n.synced} "),
Tab(text: " ${context.l10n.plain} "),
],
);

tabbar = PreferredSize(
preferredSize: tabbar.preferredSize,
child: Row(
children: [
tabbar,
const Spacer(),
Consumer(
builder: (context, ref, child) {
final playback = ref.watch(ProxyPlaylistNotifier.provider);
final lyric =
ref.watch(syncedLyricsProvider(playback.activeTrack));
final providerName = lyric.asData?.value.provider;

if (providerName == null) {
return const SizedBox.shrink();
}

return Align(
alignment: Alignment.bottomRight,
child: Text("Powered by $providerName"),
);
},
),
const Gap(5),
],
),
);

final auth = ref.watch(AuthenticationNotifier.provider);

if (auth == null) {
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/lyrics/plain_lyrics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/lyrics/zoom_controls.dart';
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
Expand Down Expand Up @@ -120,6 +119,7 @@ class PlainLyrics extends HookConsumerWidget {
lyrics == null && playlist.activeTrack == null
? "No Track being played currently"
: lyrics ?? "",
textAlign: TextAlign.center,
),
);
},
Expand Down
199 changes: 103 additions & 96 deletions lib/pages/lyrics/synced_lyrics.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
Expand Down Expand Up @@ -71,125 +75,128 @@ class SyncedLyrics extends HookConsumerWidget {
);
return Stack(
children: [
Column(
children: [
CustomScrollView(
controller: controller,
slivers: [
if (isModal != true)
Center(
child: Text(
SliverAppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
centerTitle: true,
title: Text(
playlist.activeTrack?.name ?? "Not Playing",
style: headlineTextStyle,
),
),
if (isModal != true)
Center(
child: Text(
playlist.activeTrack?.artists?.asString() ?? "",
style: mediaQuery.mdAndUp
? textTheme.headlineSmall
: textTheme.titleLarge,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(40),
child: Text(
playlist.activeTrack?.artists?.asString() ?? "",
style: mediaQuery.mdAndUp
? textTheme.headlineSmall
: textTheme.titleLarge,
),
),
),
if (lyricValue != null &&
lyricValue.lyrics.isNotEmpty &&
lyricsState.asData?.value.static != true)
Expanded(
child: ListView.builder(
controller: controller,
itemCount: lyricValue.lyrics.length,
itemBuilder: (context, index) {
final lyricSlice = lyricValue.lyrics[index];
final isActive = lyricSlice.time.inSeconds == currentTime;
SliverList.builder(
itemCount: lyricValue.lyrics.length,
itemBuilder: (context, index) {
final lyricSlice = lyricValue.lyrics[index];
final isActive = lyricSlice.time.inSeconds == currentTime;

if (isActive) {
controller.scrollToIndex(
index,
preferPosition: AutoScrollPosition.middle,
);
}
return AutoScrollTag(
key: ValueKey(index),
index: index,
controller: controller,
child: lyricSlice.text.isEmpty
? Container(
if (isActive) {
controller.scrollToIndex(
index,
preferPosition: AutoScrollPosition.middle,
);
}
return AutoScrollTag(
key: ValueKey(index),
index: index,
controller: controller,
child: lyricSlice.text.isEmpty
? Container(
padding: index == lyricValue.lyrics.length - 1
? EdgeInsets.only(
bottom: mediaQuery.size.height / 2,
)
: null,
)
: Center(
child: Padding(
padding: index == lyricValue.lyrics.length - 1
? EdgeInsets.only(
bottom: mediaQuery.size.height / 2,
? const EdgeInsets.all(8.0).copyWith(
bottom: 100,
)
: null,
)
: Center(
child: Padding(
padding: index == lyricValue.lyrics.length - 1
? const EdgeInsets.all(8.0).copyWith(
bottom: 100,
)
: const EdgeInsets.all(8.0),
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 250),
style: TextStyle(
fontWeight: isActive
? FontWeight.w500
: FontWeight.normal,
fontSize: (isActive ? 28 : 26) *
(textZoomLevel.value / 100),
),
textAlign: TextAlign.center,
child: InkWell(
onTap: () async {
final duration =
await audioPlayer.duration ??
Duration.zero;
final time = Duration(
seconds:
lyricSlice.time.inSeconds - delay,
);
if (time > duration || time.isNegative) {
return;
}
audioPlayer.seek(time);
},
child: Builder(builder: (context) {
return StrokeText(
text: lyricSlice.text,
textStyle:
DefaultTextStyle.of(context).style,
textColor: isActive
? Colors.white
: palette.bodyTextColor,
strokeColor: isActive
? Colors.black
: Colors.transparent,
);
}),
),
: const EdgeInsets.all(8.0),
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 250),
style: TextStyle(
fontWeight: isActive
? FontWeight.w500
: FontWeight.normal,
fontSize: (isActive ? 28 : 26) *
(textZoomLevel.value / 100),
),
textAlign: TextAlign.center,
child: InkWell(
onTap: () async {
final duration =
await audioPlayer.duration ??
Duration.zero;
final time = Duration(
seconds:
lyricSlice.time.inSeconds - delay,
);
if (time > duration || time.isNegative) {
return;
}
audioPlayer.seek(time);
},
child: Builder(builder: (context) {
return StrokeText(
text: lyricSlice.text,
textStyle:
DefaultTextStyle.of(context).style,
textColor: isActive
? Colors.white
: palette.bodyTextColor,
strokeColor: isActive
? Colors.black
: Colors.transparent,
);
}),
),
),
),
);
},
),
),
);
},
),
if (playlist.activeTrack != null &&
(timedLyricsQuery.isLoading || timedLyricsQuery.isRefreshing))
const Expanded(
child: ShimmerLyrics(),
)
const SliverToBoxAdapter(child: ShimmerLyrics())
else if (playlist.activeTrack != null &&
(timedLyricsQuery.hasError)) ...[
Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16),
child: Text(
context.l10n.no_lyrics_available,
style: bodyTextTheme,
textAlign: TextAlign.center,
SliverToBoxAdapter(
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16),
child: Text(
context.l10n.no_lyrics_available,
style: bodyTextTheme,
textAlign: TextAlign.center,
),
),
),
const Gap(26),
const Icon(SpotubeIcons.noLyrics, size: 60),
const SliverGap(26),
const SliverToBoxAdapter(
child: Icon(SpotubeIcons.noLyrics, size: 60),
),
] else if (lyricsState.asData?.value.static == true)
Expanded(
SliverFillRemaining(
child: Center(
child: RichText(
textAlign: TextAlign.center,
Expand Down
Loading

0 comments on commit 5afe823

Please sign in to comment.