mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
crash in windows bug fix (hotkey_manager)
Player shuffle unshuffle support Lyrics page infinite loading on no accessToken fixed
This commit is contained in:
parent
07b1891cb4
commit
7cbbd0346e
21
.vscode/c_cpp_properties.json
vendored
Normal file
21
.vscode/c_cpp_properties.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Win32",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/**"
|
||||||
|
],
|
||||||
|
"defines": [
|
||||||
|
"_DEBUG",
|
||||||
|
"UNICODE",
|
||||||
|
"_UNICODE"
|
||||||
|
],
|
||||||
|
"windowsSdkVersion": "10.0.19041.0",
|
||||||
|
"compilerPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30133\\bin\\Hostx64\\x64\\cl.exe",
|
||||||
|
"cStandard": "c17",
|
||||||
|
"cppStandard": "c++17",
|
||||||
|
"intelliSenseMode": "windows-msvc-x64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1 +1,3 @@
|
|||||||
{}
|
{
|
||||||
|
"cmake.configureOnOpen": false
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotube/components/Settings.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';
|
||||||
@ -20,13 +21,16 @@ class _LyricsState extends State<Lyrics> {
|
|||||||
Playback playback = context.watch<Playback>();
|
Playback playback = context.watch<Playback>();
|
||||||
UserPreferences userPreferences = context.watch<UserPreferences>();
|
UserPreferences userPreferences = context.watch<UserPreferences>();
|
||||||
|
|
||||||
|
bool hasToken = (userPreferences.geniusAccessToken != null ||
|
||||||
|
(userPreferences.geniusAccessToken?.isNotEmpty ?? false));
|
||||||
|
|
||||||
if (playback.currentTrack != null &&
|
if (playback.currentTrack != null &&
|
||||||
userPreferences.geniusAccessToken != null &&
|
hasToken &&
|
||||||
playback.currentTrack!.id != _lyrics["id"]) {
|
playback.currentTrack!.id != _lyrics["id"]) {
|
||||||
getLyrics(
|
getLyrics(
|
||||||
playback.currentTrack!.name!,
|
playback.currentTrack!.name!,
|
||||||
artistsToString(playback.currentTrack!.artists ?? []),
|
artistsToString(playback.currentTrack!.artists ?? []),
|
||||||
apiKey: userPreferences.geniusAccessToken,
|
apiKey: userPreferences.geniusAccessToken!,
|
||||||
optimizeQuery: true,
|
optimizeQuery: true,
|
||||||
).then((lyrics) {
|
).then((lyrics) {
|
||||||
if (lyrics != null) {
|
if (lyrics != null) {
|
||||||
@ -44,12 +48,32 @@ class _LyricsState extends State<Lyrics> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_lyrics["lyrics"] == null && playback.currentTrack != null) {
|
if (_lyrics["lyrics"] == null && playback.currentTrack != null) {
|
||||||
|
if (!hasToken) {
|
||||||
|
return Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Text("Genius lyrics API access token isn't set"),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return const Settings();
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: const Text("Add Access Token"))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
return const Expanded(
|
return const Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator.adaptive(),
|
child: CircularProgressIndicator.adaptive(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
@ -30,70 +31,24 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
double _volume = 0;
|
double _volume = 0;
|
||||||
|
|
||||||
final List<HotKey> _hotKeys = [];
|
late List<GlobalKeyActions> _hotKeys;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
try {
|
||||||
player = AudioPlayer();
|
super.initState();
|
||||||
WidgetsBinding.instance?.addObserver(this);
|
player = AudioPlayer();
|
||||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
|
_hotKeys = [
|
||||||
try {
|
GlobalKeyActions(
|
||||||
setState(() {
|
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
||||||
_volume = player.volume;
|
_playOrPause,
|
||||||
});
|
),
|
||||||
player.playingStream.listen((playing) async {
|
// causaes crash in Windows for aquiring global hotkey of
|
||||||
setState(() {
|
// keyboard media buttons
|
||||||
_isPlaying = playing;
|
if (!Platform.isWindows) ...[
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
player.durationStream.listen((duration) async {
|
|
||||||
if (duration != null) {
|
|
||||||
// Actually things doesn't work all the time as they were
|
|
||||||
// described. So instead of listening to a `playback.ready`
|
|
||||||
// stream, it has to listen to duration stream since duration
|
|
||||||
// is always added to the Stream sink after all icyMetadata has
|
|
||||||
// been loaded thus indicating buffering started
|
|
||||||
if (duration != Duration.zero && duration != _duration) {
|
|
||||||
// this line is for prev/next or already playing playlist
|
|
||||||
if (player.playing) await player.pause();
|
|
||||||
await player.play();
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_duration = duration;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
player.processingStateStream.listen((event) async {
|
|
||||||
try {
|
|
||||||
if (event == ProcessingState.completed && _currentTrackId != null) {
|
|
||||||
_movePlaylistPositionBy(1);
|
|
||||||
}
|
|
||||||
} catch (e, stack) {
|
|
||||||
print("[PrecessingStateStreamListener] $e");
|
|
||||||
print(stack);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
playOrPause(key) async {
|
|
||||||
try {
|
|
||||||
_isPlaying ? await player.pause() : await player.play();
|
|
||||||
} catch (e, stack) {
|
|
||||||
print("[PlayPauseShortcut] $e");
|
|
||||||
print(stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GlobalKeyActions> keyWithActions = [
|
|
||||||
GlobalKeyActions(
|
|
||||||
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
|
||||||
playOrPause,
|
|
||||||
),
|
|
||||||
GlobalKeyActions(
|
GlobalKeyActions(
|
||||||
HotKey(KeyCode.mediaPlayPause),
|
HotKey(KeyCode.mediaPlayPause),
|
||||||
playOrPause,
|
_playOrPause,
|
||||||
),
|
),
|
||||||
GlobalKeyActions(HotKey(KeyCode.mediaTrackNext), (key) async {
|
GlobalKeyActions(HotKey(KeyCode.mediaTrackNext), (key) async {
|
||||||
_movePlaylistPositionBy(1);
|
_movePlaylistPositionBy(1);
|
||||||
@ -111,27 +66,74 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
});
|
});
|
||||||
playback.reset();
|
playback.reset();
|
||||||
})
|
})
|
||||||
];
|
]
|
||||||
|
];
|
||||||
|
WidgetsBinding.instance?.addObserver(this);
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback(_init);
|
||||||
|
} catch (e, stack) {
|
||||||
|
print("[Player.initState()] $e");
|
||||||
|
print(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await Future.wait(
|
_init(Duration timeStamp) async {
|
||||||
keyWithActions.map((e) {
|
try {
|
||||||
return hotKeyManager.register(
|
setState(() {
|
||||||
e.hotKey,
|
_volume = player.volume;
|
||||||
keyDownHandler: e.onKeyDown,
|
});
|
||||||
);
|
player.playingStream.listen((playing) async {
|
||||||
}),
|
setState(() {
|
||||||
);
|
_isPlaying = playing;
|
||||||
} catch (e) {
|
});
|
||||||
print("[Player.initState()]: $e");
|
});
|
||||||
}
|
|
||||||
});
|
player.durationStream.listen((duration) async {
|
||||||
|
if (duration != null) {
|
||||||
|
// Actually things doesn't work all the time as they were
|
||||||
|
// described. So instead of listening to a `playback.ready`
|
||||||
|
// stream, it has to listen to duration stream since duration
|
||||||
|
// is always added to the Stream sink after all icyMetadata has
|
||||||
|
// been loaded thus indicating buffering started
|
||||||
|
if (duration != Duration.zero && duration != _duration) {
|
||||||
|
// this line is for prev/next or already playing playlist
|
||||||
|
if (player.playing) await player.pause();
|
||||||
|
await player.play();
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_duration = duration;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.processingStateStream.listen((event) async {
|
||||||
|
try {
|
||||||
|
if (event == ProcessingState.completed && _currentTrackId != null) {
|
||||||
|
_movePlaylistPositionBy(1);
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
print("[PrecessingStateStreamListener] $e");
|
||||||
|
print(stack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.wait(
|
||||||
|
_hotKeys.map((e) {
|
||||||
|
return hotKeyManager.register(
|
||||||
|
e.hotKey,
|
||||||
|
keyDownHandler: e.onKeyDown,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print("[Player._init()]: $e");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
WidgetsBinding.instance?.removeObserver(this);
|
WidgetsBinding.instance?.removeObserver(this);
|
||||||
player.dispose();
|
player.dispose();
|
||||||
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e)));
|
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey)));
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +147,15 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_playOrPause(key) async {
|
||||||
|
try {
|
||||||
|
_isPlaying ? await player.pause() : await player.play();
|
||||||
|
} catch (e, stack) {
|
||||||
|
print("[PlayPauseShortcut] $e");
|
||||||
|
print(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _movePlaylistPositionBy(int pos) {
|
void _movePlaylistPositionBy(int pos) {
|
||||||
Playback playback = context.read<Playback>();
|
Playback playback = context.read<Playback>();
|
||||||
if (playback.currentTrack != null && playback.currentPlaylist != null) {
|
if (playback.currentTrack != null && playback.currentPlaylist != null) {
|
||||||
@ -303,19 +314,19 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShuffle: () async {
|
onShuffle: () async {
|
||||||
|
if (playback.currentTrack == null ||
|
||||||
|
playback.currentPlaylist == null) return;
|
||||||
try {
|
try {
|
||||||
if (!_shuffled) {
|
if (!_shuffled) {
|
||||||
await player.setShuffleModeEnabled(true).then(
|
playback.currentPlaylist!.shuffle();
|
||||||
(value) => setState(() {
|
setState(() {
|
||||||
_shuffled = true;
|
_shuffled = true;
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
await player.setShuffleModeEnabled(false).then(
|
playback.currentPlaylist!.unshuffle();
|
||||||
(value) => setState(() {
|
setState(() {
|
||||||
_shuffled = false;
|
_shuffled = false;
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
print("[PlayerControls.onShuffle()] $e");
|
print("[PlayerControls.onShuffle()] $e");
|
||||||
|
@ -5,6 +5,7 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
Future<String?> connectIpc(String authUri, String redirectUri) async {
|
Future<String?> connectIpc(String authUri, String redirectUri) async {
|
||||||
try {
|
try {
|
||||||
if (await canLaunch(authUri)) {
|
if (await canLaunch(authUri)) {
|
||||||
|
print("[Launching]: $authUri");
|
||||||
await launch(authUri);
|
await launch(authUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,18 +2,35 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
|
||||||
class CurrentPlaylist {
|
class CurrentPlaylist {
|
||||||
|
List<Track>? _tempTrack;
|
||||||
List<Track> tracks;
|
List<Track> tracks;
|
||||||
String id;
|
String id;
|
||||||
String name;
|
String name;
|
||||||
String thumbnail;
|
String thumbnail;
|
||||||
CurrentPlaylist({
|
CurrentPlaylist({
|
||||||
required List<Track> this.tracks,
|
required this.tracks,
|
||||||
required String this.id,
|
required this.id,
|
||||||
required String this.name,
|
required this.name,
|
||||||
required String this.thumbnail,
|
required this.thumbnail,
|
||||||
});
|
});
|
||||||
|
|
||||||
List<String> get trackIds => tracks.map((e) => e.id!).toList();
|
List<String> get trackIds => tracks.map((e) => e.id!).toList();
|
||||||
|
|
||||||
|
void shuffle() {
|
||||||
|
// won't shuffle if already shuffled
|
||||||
|
if (_tempTrack == null) {
|
||||||
|
_tempTrack = [...tracks];
|
||||||
|
tracks.shuffle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unshuffle() {
|
||||||
|
// without _tempTracks unshuffling can't be done
|
||||||
|
if (_tempTrack != null) {
|
||||||
|
tracks = [..._tempTrack!];
|
||||||
|
_tempTrack = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Playback extends ChangeNotifier {
|
class Playback extends ChangeNotifier {
|
||||||
|
@ -16,7 +16,7 @@ class UserPreferences extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get geniusAccessToken => _geniusAccessToken;
|
String? get geniusAccessToken => _geniusAccessToken;
|
||||||
|
|
||||||
setGeniusAccessToken(String? token) {
|
setGeniusAccessToken(String? token) {
|
||||||
_geniusAccessToken = token;
|
_geniusAccessToken = token;
|
||||||
|
Loading…
Reference in New Issue
Block a user