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:provider/provider.dart';
import 'package:spotube/components/Settings.dart';
import 'package:spotube/helpers/artist-to-string.dart';
import 'package:spotube/helpers/getLyrics.dart';
import 'package:spotube/provider/Playback.dart';
@ -20,13 +21,16 @@ class _LyricsState extends State<Lyrics> {
Playback playback = context.watch<Playback>();
UserPreferences userPreferences = context.watch<UserPreferences>();
bool hasToken = (userPreferences.geniusAccessToken != null ||
(userPreferences.geniusAccessToken?.isNotEmpty ?? false));
if (playback.currentTrack != null &&
userPreferences.geniusAccessToken != null &&
hasToken &&
playback.currentTrack!.id != _lyrics["id"]) {
getLyrics(
playback.currentTrack!.name!,
artistsToString(playback.currentTrack!.artists ?? []),
apiKey: userPreferences.geniusAccessToken,
apiKey: userPreferences.geniusAccessToken!,
optimizeQuery: true,
).then((lyrics) {
if (lyrics != null) {
@ -44,12 +48,32 @@ class _LyricsState extends State<Lyrics> {
}
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(
child: Center(
child: CircularProgressIndicator.adaptive(),
),
);
}
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
@ -30,70 +31,24 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
double _volume = 0;
final List<HotKey> _hotKeys = [];
late List<GlobalKeyActions> _hotKeys;
@override
void initState() {
super.initState();
player = AudioPlayer();
WidgetsBinding.instance?.addObserver(this);
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
try {
setState(() {
_volume = player.volume;
});
player.playingStream.listen((playing) async {
setState(() {
_isPlaying = playing;
});
});
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,
),
try {
super.initState();
player = AudioPlayer();
_hotKeys = [
GlobalKeyActions(
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
_playOrPause,
),
// causaes crash in Windows for aquiring global hotkey of
// keyboard media buttons
if (!Platform.isWindows) ...[
GlobalKeyActions(
HotKey(KeyCode.mediaPlayPause),
playOrPause,
_playOrPause,
),
GlobalKeyActions(HotKey(KeyCode.mediaTrackNext), (key) async {
_movePlaylistPositionBy(1);
@ -111,27 +66,74 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
});
playback.reset();
})
];
]
];
WidgetsBinding.instance?.addObserver(this);
WidgetsBinding.instance?.addPostFrameCallback(_init);
} catch (e, stack) {
print("[Player.initState()] $e");
print(stack);
}
}
await Future.wait(
keyWithActions.map((e) {
return hotKeyManager.register(
e.hotKey,
keyDownHandler: e.onKeyDown,
);
}),
);
} catch (e) {
print("[Player.initState()]: $e");
}
});
_init(Duration timeStamp) async {
try {
setState(() {
_volume = player.volume;
});
player.playingStream.listen((playing) async {
setState(() {
_isPlaying = playing;
});
});
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
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
player.dispose();
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e)));
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey)));
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) {
Playback playback = context.read<Playback>();
if (playback.currentTrack != null && playback.currentPlaylist != null) {
@ -303,19 +314,19 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
}
},
onShuffle: () async {
if (playback.currentTrack == null ||
playback.currentPlaylist == null) return;
try {
if (!_shuffled) {
await player.setShuffleModeEnabled(true).then(
(value) => setState(() {
_shuffled = true;
}),
);
playback.currentPlaylist!.shuffle();
setState(() {
_shuffled = true;
});
} else {
await player.setShuffleModeEnabled(false).then(
(value) => setState(() {
_shuffled = false;
}),
);
playback.currentPlaylist!.unshuffle();
setState(() {
_shuffled = false;
});
}
} catch (e, stack) {
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 {
try {
if (await canLaunch(authUri)) {
print("[Launching]: $authUri");
await launch(authUri);
}

View File

@ -2,18 +2,35 @@ import 'package:flutter/cupertino.dart';
import 'package:spotify/spotify.dart';
class CurrentPlaylist {
List<Track>? _tempTrack;
List<Track> tracks;
String id;
String name;
String thumbnail;
CurrentPlaylist({
required List<Track> this.tracks,
required String this.id,
required String this.name,
required String this.thumbnail,
required this.tracks,
required this.id,
required this.name,
required this.thumbnail,
});
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 {

View File

@ -16,7 +16,7 @@ class UserPreferences extends ChangeNotifier {
}
}
get geniusAccessToken => _geniusAccessToken;
String? get geniusAccessToken => _geniusAccessToken;
setGeniusAccessToken(String? token) {
_geniusAccessToken = token;