Genius Lyrics works without access tokens

android background playback configuration added
This commit is contained in:
Kingkor Roy Tirtho 2022-03-17 21:34:52 +06:00
parent f483c59915
commit bbfb8f8522
11 changed files with 118 additions and 11 deletions

View File

@ -27,6 +27,8 @@ jobs:
- run: chmod +x /usr/local/bin/appimagetool - run: chmod +x /usr/local/bin/appimagetool
- run: pip3 install appimage-builder - run: pip3 install appimage-builder
- run: make appimage - run: make appimage
# Building Android Application
- run: flutter build apk
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: Spotube-Linux-Bundle name: Spotube-Linux-Bundle
@ -34,6 +36,7 @@ jobs:
build/Spotube-linux-x86_64.deb build/Spotube-linux-x86_64.deb
build/Spotube-linux-x86_64.tar.xz build/Spotube-linux-x86_64.tar.xz
build/Spotube-*-x86_64.AppImage build/Spotube-*-x86_64.AppImage
build/app/outputs/apk/release/app-release.apk
build_windows: build_windows:
runs-on: windows-latest runs-on: windows-latest
steps: steps:

View File

@ -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 - 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) ![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 > **Note!**: No personal data or any kind of sensitive information won't be collected from spotify. Don't believe? See the code for yourself

View File

@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="oss.krtirtho.spotube"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="oss.krtirtho.spotube">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<queries> <queries>
<!-- If your app opens https URLs --> <!-- If your app opens https URLs -->
@ -11,7 +13,7 @@
</queries> </queries>
<application android:label="Spotube" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true"> <application android:label="Spotube" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true">
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <activity android:name="com.ryanheise.audioservice.AudioServiceActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
@ -23,6 +25,19 @@
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name="com.ryanheise.audioservice.AudioService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" /> <meta-data android:name="flutterEmbedding" android:value="2" />

19
bin/create-secrets.dart Normal file
View File

@ -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<String> secrets = ${env["SECRET"]};");
}

View File

@ -3,8 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.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/artist-to-string.dart';
import 'package:spotube/helpers/getLyrics.dart'; import 'package:spotube/helpers/getLyrics.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
@ -39,7 +37,9 @@ class Lyrics extends HookConsumerWidget {
var lyricsSnapshot = useFuture(lyricsFuture); var lyricsSnapshot = useFuture(lyricsFuture);
useEffect(() { useEffect(() {
if (lyricsSnapshot.hasData && lyricsSnapshot.data != null) { if (lyricsSnapshot.hasData &&
lyricsSnapshot.data != null &&
playback.currentTrack != null) {
lyrics.value = { lyrics.value = {
"lyrics": lyricsSnapshot.data, "lyrics": lyricsSnapshot.data,
"id": playback.currentTrack!.id! "id": playback.currentTrack!.id!

View File

@ -0,0 +1,6 @@
import 'dart:math';
final Random _random = Random();
T getRandomElement<T>(List<T> list) {
return list[_random.nextInt(list.length)];
}

View File

@ -2,6 +2,8 @@ import 'dart:convert';
import 'package:html/parser.dart' as parser; import 'package:html/parser.dart' as parser;
import 'package:html/dom.dart'; import 'package:html/dom.dart';
import 'package:http/http.dart' as http; 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) { String getTitle(String title, String artist) {
return "$title $artist" return "$title $artist"
@ -47,16 +49,17 @@ Future<String?> extractLyrics(Uri url) async {
Future<List?> searchSong( Future<List?> searchSong(
String title, String title,
String artist, { String artist, {
String apiKey = "", String? apiKey,
bool optimizeQuery = false, bool optimizeQuery = false,
bool authHeader = false, bool authHeader = false,
}) async { }) async {
try { try {
if (apiKey == "" || apiKey == null) apiKey = getRandomElement(secrets);
const searchUrl = 'https://api.genius.com/search?q='; const searchUrl = 'https://api.genius.com/search?q=';
String song = optimizeQuery ? getTitle(title, artist) : "$title $artist"; String song = optimizeQuery ? getTitle(title, artist) : "$title $artist";
String reqUrl = "$searchUrl${Uri.encodeComponent(song)}"; String reqUrl = "$searchUrl${Uri.encodeComponent(song)}";
Map<String, String> headers = {"Authorization": 'Bearer ' + apiKey}; Map<String, String> headers = {"Authorization": 'Bearer $apiKey'};
var response = await http.get( var response = await http.get(
Uri.parse(authHeader ? reqUrl : "$reqUrl&access_token=$apiKey"), Uri.parse(authHeader ? reqUrl : "$reqUrl&access_token=$apiKey"),
headers: authHeader ? headers : null, headers: authHeader ? headers : null,
@ -100,5 +103,6 @@ Future<String?> getLyrics(
} catch (e, stack) { } catch (e, stack) {
print("[getLyrics] $e"); print("[getLyrics] $e");
print(stack); print(stack);
return null;
} }
} }

View File

@ -12,9 +12,16 @@ import 'package:spotube/models/LocalStorageKeys.dart';
import 'package:spotube/provider/AudioPlayer.dart'; import 'package:spotube/provider/AudioPlayer.dart';
import 'package:spotube/provider/ThemeProvider.dart'; import 'package:spotube/provider/ThemeProvider.dart';
import 'package:spotube/provider/YouTube.dart'; import 'package:spotube/provider/YouTube.dart';
import 'package:just_audio_background/just_audio_background.dart';
void main() async { 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(); WidgetsFlutterBinding.ensureInitialized();
await hotKeyManager.unregisterAll(); await hotKeyManager.unregisterAll();
doWhenWindowReady(() { doWhenWindowReady(() {

View File

@ -4,7 +4,10 @@ import 'package:audio_session/audio_session.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:just_audio_background/just_audio_background.dart';
import 'package:spotify/spotify.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/helpers/search-youtube.dart';
import 'package:spotube/provider/AudioPlayer.dart'; import 'package:spotube/provider/AudioPlayer.dart';
import 'package:spotube/provider/YouTube.dart'; import 'package:spotube/provider/YouTube.dart';
@ -221,10 +224,17 @@ class Playback extends ChangeNotifier {
track ??= _currentTrack; track ??= _currentTrack;
if (track != null && await _audioSession?.setActive(true) == true) { if (track != null && await _audioSession?.setActive(true) == true) {
Uri? parsedUri = Uri.tryParse(track.uri ?? ""); Uri? parsedUri = Uri.tryParse(track.uri ?? "");
final tag = MediaItem(
id: track.id!,
title: track.name!,
album: track.album?.name,
artist: artistsToString(track.artists ?? <ArtistSimple>[]),
artUri: Uri.parse(imageToUrlString(track.album?.images)),
);
if (parsedUri != null && parsedUri.hasAbsolutePath) { if (parsedUri != null && parsedUri.hasAbsolutePath) {
await player await player
.setAudioSource( .setAudioSource(
AudioSource.uri(parsedUri), AudioSource.uri(parsedUri, tag: tag),
preload: true, preload: true,
) )
.then((value) async { .then((value) async {
@ -238,7 +248,7 @@ class Playback extends ChangeNotifier {
if (setTrackUriById(track.id!, ytTrack.uri!)) { if (setTrackUriById(track.id!, ytTrack.uri!)) {
await player await player
.setAudioSource( .setAudioSource(
AudioSource.uri(Uri.parse(ytTrack.uri!)), AudioSource.uri(Uri.parse(ytTrack.uri!), tag: tag),
preload: true, preload: true,
) )
.then((value) { .then((value) {

View File

@ -29,6 +29,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" 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: audio_session:
dependency: "direct main" dependency: "direct main"
description: description:
@ -148,6 +169,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
dotenv:
dependency: "direct dev"
description:
name: dotenv
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -310,6 +338,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.20" 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: just_audio_libwinmedia:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -55,6 +55,7 @@ dependencies:
go_router: ^3.0.4 go_router: ^3.0.4
palette_generator: ^0.3.3 palette_generator: ^0.3.3
audio_session: ^0.1.6+1 audio_session: ^0.1.6+1
just_audio_background: ^0.0.1-beta.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -68,6 +69,7 @@ dev_dependencies:
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^1.0.0 flutter_lints: ^1.0.0
flutter_launcher_icons: ^0.9.2 flutter_launcher_icons: ^0.9.2
dotenv: ^3.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec