3 Commits
Author SHA1 Message Date
Illusion 197be6330b fix: show toast error and fallback icon when album art fails to load 2022-08-06 02:25:55 +03:00
Illusion 100157a8d4 feat/fix: bump to 0.2.0
* remove spotify-only restriction.
* only support players that use mediasession and media notifications.
* don't capture non-mediasession notifications.
2022-08-06 00:55:18 +03:00
Illusion 3bf7750e51 fix: better looking result item, and client-side lyrics fetching 2022-08-03 23:06:20 +03:00
6 changed files with 81 additions and 41 deletions
@@ -56,21 +56,19 @@ class NotificationListener : NotificationListenerService() {
} }
override fun onNotificationPosted(sbn: StatusBarNotification) { override fun onNotificationPosted(sbn: StatusBarNotification) {
if (sbn.packageName == SPOTIFY_PACKAGE) {
sendNotificationBroadcast(sbn, applicationContext) sendNotificationBroadcast(sbn, applicationContext)
} }
}
private fun getStopServiceNotificationAction(): PendingIntent { private fun getStopServiceNotificationAction(): PendingIntent {
val intent = Intent(MainActivity.ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val intent = Intent(MainActivity.ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0) val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
return pendingIntent return pendingIntent
} }
private fun startForegroundService() { private fun startForegroundService() {
val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle("Service Running") .setContentTitle("Service Running")
.setContentText("Play some songs on spotify to get lyrics") .setContentText("Play some songs on a supported player to get lyrics")
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setShowWhen(false) .setShowWhen(false)
.addAction(R.drawable.ic_notification, "Stop Service", getStopServiceNotificationAction()) .addAction(R.drawable.ic_notification, "Stop Service", getStopServiceNotificationAction())
@@ -103,19 +101,21 @@ class NotificationListener : NotificationListenerService() {
private fun getCurrentNotifications(context: Context) { private fun getCurrentNotifications(context: Context) {
val sbns = activeNotifications val sbns = activeNotifications
for (sbn in sbns) { for (sbn in sbns) {
if (sbn.packageName == SPOTIFY_PACKAGE) {
sendNotificationBroadcast(sbn, context) sendNotificationBroadcast(sbn, context)
} }
} }
}
private fun sendNotificationBroadcast(sbn: StatusBarNotification, context: Context) { private fun sendNotificationBroadcast(sbn: StatusBarNotification, context: Context) {
// get the media session stuff // get the media session stuff
val mediaSessionToken = sbn.notification.extras.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION) val mediaSessionToken = sbn.notification.extras.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)
val mediaCtrl = mediaSessionToken?.let { MediaController(context, it) } if (mediaSessionToken == null) {
val duration = mediaCtrl?.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.div(1000) return
val playbackState = mediaCtrl?.playbackState?.state }
val albumArt = mediaCtrl?.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
val mediaCtrl = mediaSessionToken.let { MediaController(context, it) }
val duration = mediaCtrl.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.div(1000)
val playbackState = mediaCtrl.playbackState?.state
val albumArt = mediaCtrl.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
// Retrieve package name to set as title. // Retrieve package name to set as title.
val packageName = sbn.packageName val packageName = sbn.packageName
@@ -134,7 +134,7 @@ class NotificationListener : NotificationListenerService() {
if (playbackState == PlaybackState.STATE_PAUSED) { if (playbackState == PlaybackState.STATE_PAUSED) {
val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle("Service Running") .setContentTitle("Service Running")
.setContentText("Play some songs on spotify to get lyrics") .setContentText("Play some songs on a supported player to get lyrics")
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setShowWhen(false) .setShowWhen(false)
.addAction(R.drawable.ic_notification, "Stop Service", getStopServiceNotificationAction()) .addAction(R.drawable.ic_notification, "Stop Service", getStopServiceNotificationAction())
@@ -173,7 +173,6 @@ class NotificationListener : NotificationListenerService() {
const val NOTIFICATION_PACKAGE_DURATION = "package_duration" const val NOTIFICATION_PACKAGE_DURATION = "package_duration"
const val NOTIFICATION_ID = 12251999 const val NOTIFICATION_ID = 12251999
const val NOTIFICATION_CHANNEL_ID = "Lyrics" const val NOTIFICATION_CHANNEL_ID = "Lyrics"
const val SPOTIFY_PACKAGE = "com.spotify.music"
const val START_FOREGROUND_SERVICE_ACTION = "START_SERVICE" const val START_FOREGROUND_SERVICE_ACTION = "START_SERVICE"
} }
} }
+11 -12
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert; import 'dart:convert' as convert;
import 'package:web_scraper/web_scraper.dart';
class LyricsPage extends StatefulWidget { class LyricsPage extends StatefulWidget {
const LyricsPage({ const LyricsPage({
@@ -31,21 +31,20 @@ class _LyricsPageState extends State<LyricsPage> {
Future<void> initPlatformState() async { Future<void> initPlatformState() async {
setState(() => fetching = true); setState(() => fetching = true);
final uri = Uri.https('api.illusionman1212.tech', '/lyrics', { final webScraper = WebScraper('https://genius.com');
'songUrl': widget.url,
});
var response = await http.get(uri);
if (response.statusCode == 200) {
final jsonRes = convert.jsonDecode(response.body) as Map<String, dynamic>;
String path = widget.url.split('genius.com/')[1];
String fullLyrics = '';
if (await webScraper.loadWebPage('/$path?react=1')) {
webScraper.loadFromString(webScraper.getPageContent().replaceAll('<br>', '\n'));
var element = webScraper.getElement("div[data-lyrics-container]", ['data-lyrics-container']);
for (var section in element) {
fullLyrics += section['title'] + '\n';
}
setState(() { setState(() {
lyrics = fullLyrics;
fetching = false; fetching = false;
lyrics = jsonRes['lyrics'];
}); });
} else {
print('error while GETing lyrics');
setState(() => fetching = false);
} }
} }
+2 -2
View File
@@ -44,14 +44,14 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
@override @override
void initState() { void initState() {
WidgetsBinding.instance?.addObserver(this); WidgetsBinding.instance.addObserver(this);
super.initState(); super.initState();
initPlatformState(); initPlatformState();
} }
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance?.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
} }
+15 -3
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'lyrics.dart'; import 'lyrics.dart';
@@ -125,7 +126,7 @@ class _SearchResultState extends State<SearchResult> {
onLongPress: () => _showOptionsBottomSheet(), onLongPress: () => _showOptionsBottomSheet(),
child: Ink( child: Ink(
height: 75, height: 75,
color: Colors.black, color: Colors.white10,
child: Row( child: Row(
children: [ children: [
Container( Container(
@@ -135,6 +136,17 @@ class _SearchResultState extends State<SearchResult> {
width: 75, width: 75,
height: 75, height: 75,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
Fluttertoast.showToast(
msg: 'Failed to load album art for: ${widget.title}',
toastLength: Toast.LENGTH_LONG);
return const Center(
child: SizedBox(
width: 75,
height: 75,
child: Icon(Icons.broken_image, size: 40)),
);
},
), ),
), ),
Flexible( Flexible(
@@ -145,7 +157,7 @@ class _SearchResultState extends State<SearchResult> {
Text( Text(
widget.title, widget.title,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.black,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -154,7 +166,7 @@ class _SearchResultState extends State<SearchResult> {
Text( Text(
widget.artist, widget.artist,
style: const TextStyle( style: const TextStyle(
color: Colors.white70, color: Colors.black54,
fontSize: 14, fontSize: 14,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
+37 -9
View File
@@ -49,7 +49,14 @@ packages:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" version: "1.16.0"
csslib:
dependency: transitive
description:
name: csslib
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.2"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@@ -63,7 +70,7 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@@ -121,6 +128,20 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.9"
html:
dependency: transitive
description:
name: html
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -141,7 +162,7 @@ packages:
name: js name: js
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.3" version: "0.6.4"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -162,7 +183,7 @@ packages:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.3" version: "0.1.4"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -176,7 +197,7 @@ packages:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@@ -216,7 +237,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@@ -251,7 +272,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.8" version: "0.4.9"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:
@@ -328,7 +349,14 @@ packages:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
web_scraper:
dependency: "direct main"
description:
name: web_scraper
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@@ -344,5 +372,5 @@ packages:
source: hosted source: hosted
version: "5.3.1" version: "5.3.1"
sdks: sdks:
dart: ">=2.16.1 <3.0.0" dart: ">=2.17.0-0 <3.0.0"
flutter: ">=2.10.0" flutter: ">=2.10.0"
+3 -1
View File
@@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# 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
version: 0.1.1+1 version: 0.2.1+1
environment: environment:
sdk: ">=2.16.1 <3.0.0" sdk: ">=2.16.1 <3.0.0"
@@ -33,6 +33,8 @@ dependencies:
http: ^0.13.4 http: ^0.13.4
flutter_local_notifications: ^9.3.2 flutter_local_notifications: ^9.3.2
url_launcher: ^6.0.20 url_launcher: ^6.0.20
web_scraper: ^0.1.4
fluttertoast: ^8.0.9
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: