crash in windows bug fix (hotkey_manager)

Player shuffle unshuffle support
Lyrics page infinite loading on no accessToken fixed
This commit is contained in:
KR Tirtho 2022-01-13 12:27:39 +06:00
parent 07b1891cb4
commit 7cbbd0346e
7 changed files with 167 additions and 91 deletions

21
.vscode/c_cpp_properties.json vendored Normal file
View 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
}

View File

@ -1 +1,3 @@
{} {
"cmake.configureOnOpen": false
}

View File

@ -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,

View File

@ -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");

View File

@ -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);
} }

View File

@ -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 {

View File

@ -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;