From bbfb8f85225ae7aa1efc38e6c7a4c22f97516a2d Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 17 Mar 2022 21:34:52 +0600 Subject: [PATCH] Genius Lyrics works without access tokens android background playback configuration added --- .github/workflows/flutter-build.yml | 3 ++ README.md | 10 +++++-- android/app/src/main/AndroidManifest.xml | 17 +++++++++++- bin/create-secrets.dart | 19 +++++++++++++ lib/components/Lyrics.dart | 6 ++-- lib/helpers/get-random-element.dart | 6 ++++ lib/helpers/getLyrics.dart | 8 ++++-- lib/main.dart | 9 +++++- lib/provider/Playback.dart | 14 ++++++++-- pubspec.lock | 35 ++++++++++++++++++++++++ pubspec.yaml | 2 ++ 11 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 bin/create-secrets.dart create mode 100644 lib/helpers/get-random-element.dart diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index 84e985e2..3253c7ab 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -27,6 +27,8 @@ jobs: - run: chmod +x /usr/local/bin/appimagetool - run: pip3 install appimage-builder - run: make appimage + # Building Android Application + - run: flutter build apk - uses: actions/upload-artifact@v2 with: name: Spotube-Linux-Bundle @@ -34,6 +36,7 @@ jobs: build/Spotube-linux-x86_64.deb build/Spotube-linux-x86_64.tar.xz build/Spotube-*-x86_64.AppImage + build/app/outputs/apk/release/app-release.apk build_windows: runs-on: windows-latest steps: diff --git a/README.md b/README.md index 128b1d34..861a40ef 100644 --- a/README.md +++ b/README.md @@ -121,9 +121,15 @@ You need a spotify account & a developer app for - Click on **SHOW CLIENT SECRET** to reveal the **clientSecret**. Then copy the **clientID**, **clientSecret** & paste in the **Spotube's** respective fields ![step-4](https://user-images.githubusercontent.com/61944859/111769501-7fe31e80-88d3-11eb-8fc1-f3655dbd4711.png) -Also, you need a [genius](https://genius.com) account for **lyrics** & a API Client for +**Setup Genius Lyrics (Optional)** -- accessToken +- Signup/Login into [genius](https://genius.com/signup) for **lyrics** +- Go To [Genius Developer Portal](https://genius.com/api-clients/new) for creating an API client + ![Step 2](https://user-images.githubusercontent.com/61944859/158823216-b4942731-c4c5-46c8-8b60-82a372b51cc5.png) +- Generate & copy access token + ![Step 3](https://user-images.githubusercontent.com/61944859/158822817-f04da060-3094-4a3b-8ace-a936d0cda8db.png) +- Paste the copied access token in Spotube's Settings + ![Step 4](https://user-images.githubusercontent.com/61944859/158823984-17f08534-5c92-41bc-918a-23194aad00f5.png) > **Note!**: No personal data or any kind of sensitive information won't be collected from spotify. Don't believe? See the code for yourself diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index be74f480..5d1d8687 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + @@ -11,7 +13,7 @@ - + diff --git a/bin/create-secrets.dart b/bin/create-secrets.dart new file mode 100644 index 00000000..a16d89ee --- /dev/null +++ b/bin/create-secrets.dart @@ -0,0 +1,19 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:dotenv/dotenv.dart'; + +void main() async { + load(); + final bool hasKey = env.containsKey("SECRET"); + final val = hasKey ? jsonDecode(env["SECRET"]!) : null; + if (!hasKey || (hasKey && val is! List)) { + throw Exception( + "'SECRET' Environmental Variable isn't configured properly"); + } + + await File(path.join( + Directory.current.path, "lib/models/generated_secrets.dart")) + .writeAsString("final List secrets = ${env["SECRET"]};"); +} diff --git a/lib/components/Lyrics.dart b/lib/components/Lyrics.dart index 16cf56f5..194e6134 100644 --- a/lib/components/Lyrics.dart +++ b/lib/components/Lyrics.dart @@ -3,8 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; -import 'package:spotube/components/Settings.dart'; -import 'package:spotube/components/Shared/SpotubePageRoute.dart'; import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/getLyrics.dart'; import 'package:spotube/provider/Playback.dart'; @@ -39,7 +37,9 @@ class Lyrics extends HookConsumerWidget { var lyricsSnapshot = useFuture(lyricsFuture); useEffect(() { - if (lyricsSnapshot.hasData && lyricsSnapshot.data != null) { + if (lyricsSnapshot.hasData && + lyricsSnapshot.data != null && + playback.currentTrack != null) { lyrics.value = { "lyrics": lyricsSnapshot.data, "id": playback.currentTrack!.id! diff --git a/lib/helpers/get-random-element.dart b/lib/helpers/get-random-element.dart new file mode 100644 index 00000000..47390678 --- /dev/null +++ b/lib/helpers/get-random-element.dart @@ -0,0 +1,6 @@ +import 'dart:math'; + +final Random _random = Random(); +T getRandomElement(List list) { + return list[_random.nextInt(list.length)]; +} diff --git a/lib/helpers/getLyrics.dart b/lib/helpers/getLyrics.dart index b7034f8d..4ab5491c 100644 --- a/lib/helpers/getLyrics.dart +++ b/lib/helpers/getLyrics.dart @@ -2,6 +2,8 @@ import 'dart:convert'; import 'package:html/parser.dart' as parser; import 'package:html/dom.dart'; import 'package:http/http.dart' as http; +import 'package:spotube/helpers/get-random-element.dart'; +import 'package:spotube/models/generated_secrets.dart'; String getTitle(String title, String artist) { return "$title $artist" @@ -47,16 +49,17 @@ Future extractLyrics(Uri url) async { Future searchSong( String title, String artist, { - String apiKey = "", + String? apiKey, bool optimizeQuery = false, bool authHeader = false, }) async { try { + if (apiKey == "" || apiKey == null) apiKey = getRandomElement(secrets); const searchUrl = 'https://api.genius.com/search?q='; String song = optimizeQuery ? getTitle(title, artist) : "$title $artist"; String reqUrl = "$searchUrl${Uri.encodeComponent(song)}"; - Map headers = {"Authorization": 'Bearer ' + apiKey}; + Map headers = {"Authorization": 'Bearer $apiKey'}; var response = await http.get( Uri.parse(authHeader ? reqUrl : "$reqUrl&access_token=$apiKey"), headers: authHeader ? headers : null, @@ -100,5 +103,6 @@ Future getLyrics( } catch (e, stack) { print("[getLyrics] $e"); print(stack); + return null; } } diff --git a/lib/main.dart b/lib/main.dart index 25918053..e51be40e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,9 +12,16 @@ import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/provider/AudioPlayer.dart'; import 'package:spotube/provider/ThemeProvider.dart'; import 'package:spotube/provider/YouTube.dart'; +import 'package:just_audio_background/just_audio_background.dart'; void main() async { - if (!Platform.isAndroid && !Platform.isIOS) { + if (Platform.isAndroid || Platform.isIOS) { + await JustAudioBackground.init( + androidNotificationChannelId: 'oss.krtirtho.Spotube', + androidNotificationChannelName: 'Spotube', + androidNotificationOngoing: true, + ); + } else { WidgetsFlutterBinding.ensureInitialized(); await hotKeyManager.unregisterAll(); doWhenWindowReady(() { diff --git a/lib/provider/Playback.dart b/lib/provider/Playback.dart index f87a0046..27f9a468 100644 --- a/lib/provider/Playback.dart +++ b/lib/provider/Playback.dart @@ -4,7 +4,10 @@ import 'package:audio_session/audio_session.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:just_audio_background/just_audio_background.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/helpers/artist-to-string.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/search-youtube.dart'; import 'package:spotube/provider/AudioPlayer.dart'; import 'package:spotube/provider/YouTube.dart'; @@ -221,10 +224,17 @@ class Playback extends ChangeNotifier { track ??= _currentTrack; if (track != null && await _audioSession?.setActive(true) == true) { Uri? parsedUri = Uri.tryParse(track.uri ?? ""); + final tag = MediaItem( + id: track.id!, + title: track.name!, + album: track.album?.name, + artist: artistsToString(track.artists ?? []), + artUri: Uri.parse(imageToUrlString(track.album?.images)), + ); if (parsedUri != null && parsedUri.hasAbsolutePath) { await player .setAudioSource( - AudioSource.uri(parsedUri), + AudioSource.uri(parsedUri, tag: tag), preload: true, ) .then((value) async { @@ -238,7 +248,7 @@ class Playback extends ChangeNotifier { if (setTrackUriById(track.id!, ytTrack.uri!)) { await player .setAudioSource( - AudioSource.uri(Uri.parse(ytTrack.uri!)), + AudioSource.uri(Uri.parse(ytTrack.uri!), tag: tag), preload: true, ) .then((value) { diff --git a/pubspec.lock b/pubspec.lock index bfa185d6..1b874a9b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,6 +29,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" + audio_service: + dependency: transitive + description: + name: audio_service + url: "https://pub.dartlang.org" + source: hosted + version: "0.18.4" + audio_service_platform_interface: + dependency: transitive + description: + name: audio_service_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + audio_service_web: + dependency: transitive + description: + name: audio_service_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1" audio_session: dependency: "direct main" description: @@ -148,6 +169,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + dotenv: + dependency: "direct dev" + description: + name: dotenv + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" fake_async: dependency: transitive description: @@ -310,6 +338,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.9.20" + just_audio_background: + dependency: "direct main" + description: + name: just_audio_background + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1-beta.5" just_audio_libwinmedia: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index beef5e68..429bfe25 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dependencies: go_router: ^3.0.4 palette_generator: ^0.3.3 audio_session: ^0.1.6+1 + just_audio_background: ^0.0.1-beta.5 dev_dependencies: flutter_test: @@ -68,6 +69,7 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^1.0.0 flutter_launcher_icons: ^0.9.2 + dotenv: ^3.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec