mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
re-initiate OAuth process on Refresh Token revoke
YouTube & Spotify track matching accuracy increase
This commit is contained in:
parent
e1c9e6fb29
commit
93d95cb309
@ -4,6 +4,7 @@ import 'package:flutter/material.dart' hide Page;
|
|||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:oauth2/oauth2.dart' show AuthorizationException;
|
||||||
import 'package:spotify/spotify.dart' hide Image;
|
import 'package:spotify/spotify.dart' hide Image;
|
||||||
import 'package:spotube/components/CategoryCard.dart';
|
import 'package:spotube/components/CategoryCard.dart';
|
||||||
import 'package:spotube/components/Login.dart';
|
import 'package:spotube/components/Login.dart';
|
||||||
@ -12,6 +13,7 @@ import 'package:spotube/components/PageWindowTitleBar.dart';
|
|||||||
import 'package:spotube/components/Player.dart' as player;
|
import 'package:spotube/components/Player.dart' as player;
|
||||||
import 'package:spotube/components/Settings.dart';
|
import 'package:spotube/components/Settings.dart';
|
||||||
import 'package:spotube/components/UserLibrary.dart';
|
import 'package:spotube/components/UserLibrary.dart';
|
||||||
|
import 'package:spotube/helpers/oauth-login.dart';
|
||||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||||
import 'package:spotube/models/sideBarTiles.dart';
|
import 'package:spotube/models/sideBarTiles.dart';
|
||||||
import 'package:spotube/provider/Auth.dart';
|
import 'package:spotube/provider/Auth.dart';
|
||||||
@ -42,18 +44,20 @@ class _HomeState extends State<Home> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
|
||||||
|
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
||||||
|
String? clientId = localStorage.getString(LocalStorageKeys.clientId);
|
||||||
|
String? clientSecret =
|
||||||
|
localStorage.getString(LocalStorageKeys.clientSecret);
|
||||||
|
String? accessToken =
|
||||||
|
localStorage.getString(LocalStorageKeys.accessToken);
|
||||||
|
String? refreshToken =
|
||||||
|
localStorage.getString(LocalStorageKeys.refreshToken);
|
||||||
|
String? expirationStr =
|
||||||
|
localStorage.getString(LocalStorageKeys.expiration);
|
||||||
|
DateTime? expiration =
|
||||||
|
expirationStr != null ? DateTime.parse(expirationStr) : null;
|
||||||
try {
|
try {
|
||||||
Auth authProvider = context.read<Auth>();
|
Auth authProvider = context.read<Auth>();
|
||||||
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
|
||||||
var clientId = localStorage.getString(LocalStorageKeys.clientId);
|
|
||||||
var clientSecret =
|
|
||||||
localStorage.getString(LocalStorageKeys.clientSecret);
|
|
||||||
var accessToken = localStorage.getString(LocalStorageKeys.accessToken);
|
|
||||||
var refreshToken =
|
|
||||||
localStorage.getString(LocalStorageKeys.refreshToken);
|
|
||||||
var expirationStr = localStorage.getString(LocalStorageKeys.expiration);
|
|
||||||
var expiration =
|
|
||||||
expirationStr != null ? DateTime.parse(expirationStr) : null;
|
|
||||||
|
|
||||||
if (clientId != null && clientSecret != null) {
|
if (clientId != null && clientSecret != null) {
|
||||||
SpotifyApi spotifyApi = SpotifyApi(
|
SpotifyApi spotifyApi = SpotifyApi(
|
||||||
@ -103,8 +107,17 @@ class _HomeState extends State<Home> {
|
|||||||
_pagingController.error = e;
|
_pagingController.error = e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} on AuthorizationException catch (e) {
|
||||||
print("[login state error]: $e");
|
if (clientId != null && clientSecret != null) {
|
||||||
|
oauthLogin(
|
||||||
|
context,
|
||||||
|
clientId: clientId,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
print("[Home.initState]: $e");
|
||||||
|
print(stack);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:spotify/spotify.dart' hide Image;
|
|
||||||
import 'package:spotube/components/Home.dart';
|
|
||||||
import 'package:spotube/components/PageWindowTitleBar.dart';
|
import 'package:spotube/components/PageWindowTitleBar.dart';
|
||||||
import 'package:spotube/helpers/server_ipc.dart';
|
import 'package:spotube/helpers/oauth-login.dart';
|
||||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
|
||||||
import 'package:spotube/provider/Auth.dart';
|
import 'package:spotube/provider/Auth.dart';
|
||||||
|
|
||||||
const redirectUri = "http://localhost:4304/auth/spotify/callback";
|
|
||||||
|
|
||||||
class Login extends StatefulWidget {
|
class Login extends StatefulWidget {
|
||||||
|
const Login({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_LoginState createState() => _LoginState();
|
_LoginState createState() => _LoginState();
|
||||||
}
|
}
|
||||||
@ -19,9 +15,6 @@ class _LoginState extends State<Login> {
|
|||||||
String clientId = "";
|
String clientId = "";
|
||||||
String clientSecret = "";
|
String clientSecret = "";
|
||||||
bool _fieldError = false;
|
bool _fieldError = false;
|
||||||
String? accessToken;
|
|
||||||
String? refreshToken;
|
|
||||||
DateTime? expiration;
|
|
||||||
|
|
||||||
handleLogin(Auth authState) async {
|
handleLogin(Auth authState) async {
|
||||||
try {
|
try {
|
||||||
@ -30,48 +23,7 @@ class _LoginState extends State<Login> {
|
|||||||
_fieldError = true;
|
_fieldError = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
final credentials = SpotifyApiCredentials(clientId, clientSecret);
|
await oauthLogin(context, clientId: clientId, clientSecret: clientSecret);
|
||||||
final grant = SpotifyApi.authorizationCodeGrant(credentials);
|
|
||||||
|
|
||||||
final authUri = grant.getAuthorizationUrl(Uri.parse(redirectUri),
|
|
||||||
scopes: spotifyScopes);
|
|
||||||
|
|
||||||
final responseUri = await connectIpc(authUri.toString(), redirectUri);
|
|
||||||
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
|
||||||
if (responseUri != null) {
|
|
||||||
final SpotifyApi spotify =
|
|
||||||
SpotifyApi.fromAuthCodeGrant(grant, responseUri);
|
|
||||||
var credentials = await spotify.getCredentials();
|
|
||||||
if (credentials.accessToken != null) {
|
|
||||||
accessToken = credentials.accessToken;
|
|
||||||
await localStorage.setString(
|
|
||||||
LocalStorageKeys.accessToken, credentials.accessToken!);
|
|
||||||
}
|
|
||||||
if (credentials.refreshToken != null) {
|
|
||||||
refreshToken = credentials.refreshToken;
|
|
||||||
await localStorage.setString(
|
|
||||||
LocalStorageKeys.refreshToken, credentials.refreshToken!);
|
|
||||||
}
|
|
||||||
if (credentials.expiration != null) {
|
|
||||||
expiration = credentials.expiration;
|
|
||||||
await localStorage.setString(LocalStorageKeys.expiration,
|
|
||||||
credentials.expiration?.toString() ?? "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await localStorage.setString(LocalStorageKeys.clientId, clientId);
|
|
||||||
await localStorage.setString(
|
|
||||||
LocalStorageKeys.clientSecret,
|
|
||||||
clientSecret,
|
|
||||||
);
|
|
||||||
authState.setAuthState(
|
|
||||||
clientId: clientId,
|
|
||||||
clientSecret: clientSecret,
|
|
||||||
accessToken: accessToken,
|
|
||||||
refreshToken: refreshToken,
|
|
||||||
expiration: expiration,
|
|
||||||
isLoggedIn: true,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("[Login.handleLogin] $e");
|
print("[Login.handleLogin] $e");
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import 'package:spotube/provider/Playback.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||||
|
|
||||||
class Player extends StatefulWidget {
|
class Player extends StatefulWidget {
|
||||||
const Player({Key? key}) : super(key: key);
|
const Player({Key? key}) : super(key: key);
|
||||||
@ -32,6 +33,8 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
double _volume = 0;
|
double _volume = 0;
|
||||||
|
|
||||||
|
late YoutubeExplode youtube;
|
||||||
|
|
||||||
late List<GlobalKeyActions> _hotKeys;
|
late List<GlobalKeyActions> _hotKeys;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -39,6 +42,7 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
try {
|
try {
|
||||||
super.initState();
|
super.initState();
|
||||||
player = AudioPlayer();
|
player = AudioPlayer();
|
||||||
|
youtube = YoutubeExplode();
|
||||||
_hotKeys = [
|
_hotKeys = [
|
||||||
GlobalKeyActions(
|
GlobalKeyActions(
|
||||||
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
||||||
@ -134,6 +138,7 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
WidgetsBinding.instance?.removeObserver(this);
|
WidgetsBinding.instance?.removeObserver(this);
|
||||||
player.dispose();
|
player.dispose();
|
||||||
|
youtube.close();
|
||||||
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey)));
|
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey)));
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -200,7 +205,7 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var ytTrack = await toYoutubeTrack(currentTrack);
|
var ytTrack = await toYoutubeTrack(youtube, currentTrack);
|
||||||
if (playback.setTrackUriById(currentTrack.id!, ytTrack.uri!)) {
|
if (playback.setTrackUriById(currentTrack.id!, ytTrack.uri!)) {
|
||||||
await player
|
await player
|
||||||
.setAudioSource(AudioSource.uri(Uri.parse(ytTrack.uri!)))
|
.setAudioSource(AudioSource.uri(Uri.parse(ytTrack.uri!)))
|
||||||
|
66
lib/helpers/oauth-login.dart
Normal file
66
lib/helpers/oauth-login.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/Home.dart';
|
||||||
|
import 'package:spotube/helpers/server_ipc.dart';
|
||||||
|
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||||
|
import 'package:spotube/provider/Auth.dart';
|
||||||
|
|
||||||
|
const redirectUri = "http://localhost:4304/auth/spotify/callback";
|
||||||
|
|
||||||
|
Future<void> oauthLogin(BuildContext context,
|
||||||
|
{required String clientId, required String clientSecret}) async {
|
||||||
|
try {
|
||||||
|
String? accessToken;
|
||||||
|
String? refreshToken;
|
||||||
|
DateTime? expiration;
|
||||||
|
final credentials = SpotifyApiCredentials(clientId, clientSecret);
|
||||||
|
final grant = SpotifyApi.authorizationCodeGrant(credentials);
|
||||||
|
|
||||||
|
final authUri = grant.getAuthorizationUrl(Uri.parse(redirectUri),
|
||||||
|
scopes: spotifyScopes);
|
||||||
|
|
||||||
|
final responseUri = await connectIpc(authUri.toString(), redirectUri);
|
||||||
|
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
||||||
|
if (responseUri != null) {
|
||||||
|
final SpotifyApi spotify =
|
||||||
|
SpotifyApi.fromAuthCodeGrant(grant, responseUri);
|
||||||
|
var credentials = await spotify.getCredentials();
|
||||||
|
if (credentials.accessToken != null) {
|
||||||
|
accessToken = credentials.accessToken;
|
||||||
|
await localStorage.setString(
|
||||||
|
LocalStorageKeys.accessToken, credentials.accessToken!);
|
||||||
|
}
|
||||||
|
if (credentials.refreshToken != null) {
|
||||||
|
refreshToken = credentials.refreshToken;
|
||||||
|
await localStorage.setString(
|
||||||
|
LocalStorageKeys.refreshToken, credentials.refreshToken!);
|
||||||
|
}
|
||||||
|
if (credentials.expiration != null) {
|
||||||
|
expiration = credentials.expiration;
|
||||||
|
await localStorage.setString(LocalStorageKeys.expiration,
|
||||||
|
credentials.expiration?.toString() ?? "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await localStorage.setString(LocalStorageKeys.clientId, clientId);
|
||||||
|
await localStorage.setString(
|
||||||
|
LocalStorageKeys.clientSecret,
|
||||||
|
clientSecret,
|
||||||
|
);
|
||||||
|
|
||||||
|
Provider.of<Auth>(context, listen: false).setAuthState(
|
||||||
|
clientId: clientId,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
accessToken: accessToken,
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
expiration: expiration,
|
||||||
|
isLoggedIn: true,
|
||||||
|
);
|
||||||
|
} catch (e, stack) {
|
||||||
|
print("[oauthLogin()] $e");
|
||||||
|
print(stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||||
|
|
||||||
YoutubeExplode youtube = YoutubeExplode();
|
Future<Track> toYoutubeTrack(YoutubeExplode youtube, Track track) async {
|
||||||
Future<Track> toYoutubeTrack(Track track) async {
|
|
||||||
var artistsName = track.artists?.map((ar) => ar.name).toList() ?? [];
|
var artistsName = track.artists?.map((ar) => ar.name).toList() ?? [];
|
||||||
String queryString =
|
String queryString =
|
||||||
"${artistsName.first} - ${track.name}${artistsName.length > 1 ? " feat. ${artistsName.sublist(1).join(" ")}" : ""}";
|
"${artistsName.first} - ${track.name}${artistsName.length > 1 ? " feat. ${artistsName.sublist(1).join(" ")}" : ""}";
|
||||||
@ -10,8 +9,11 @@ Future<Track> toYoutubeTrack(Track track) async {
|
|||||||
SearchList videos = await youtube.search.getVideos(queryString);
|
SearchList videos = await youtube.search.getVideos(queryString);
|
||||||
|
|
||||||
List<Video> matchedVideos = videos.where((video) {
|
List<Video> matchedVideos = videos.where((video) {
|
||||||
return video.title.contains(track.name!) &&
|
// the find should be lazy thus everything case insensitive
|
||||||
(track.artists?.every((artist) => video.title.contains(artist.name!)) ??
|
return video.title.toLowerCase().contains(track.name!.toLowerCase()) &&
|
||||||
|
(track.artists?.every((artist) => video.title
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(artist.name!.toLowerCase())) ??
|
||||||
false);
|
false);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user