mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-29 06:19:31 +00:00
feat: error reporting with logs (#45)
* feat: add ability to get logs file from ui * test: add unit test for log line parsing in logs_provider * refactor: update all logs to obfuscate sensitive information * feat: generate dynamic zip file name for logs export * feat: enhance logging in audiobook player and provider for better debugging * refactor: extract user display logic into UserBar widget for offline access of settings and logs * feat: add About section with app metadata and source code link in YouPage
This commit is contained in:
parent
7b0c2c4b88
commit
35a2d7cfce
44 changed files with 861 additions and 176 deletions
|
|
@ -11,38 +11,38 @@ import 'package:shelfsdk/audiobookshelf_api.dart';
|
|||
import 'package:vaani/settings/app_settings_provider.dart';
|
||||
import 'package:vaani/settings/models/app_settings.dart';
|
||||
import 'package:vaani/shared/extensions/model_conversions.dart';
|
||||
import 'package:vaani/shared/extensions/obfuscation.dart';
|
||||
|
||||
final _logger = Logger('AudiobookPlayer');
|
||||
|
||||
/// returns the sum of the duration of all the previous tracks before the [index]
|
||||
Duration sumOfTracks(BookExpanded book, int? index) {
|
||||
_logger.fine('Calculating sum of tracks for index: $index');
|
||||
// return 0 if index is less than 0
|
||||
if (index == null || index < 0) {
|
||||
_logger.warning('Index is null or less than 0, returning 0');
|
||||
return Duration.zero;
|
||||
}
|
||||
return book.tracks.sublist(0, index).fold<Duration>(Duration.zero,
|
||||
(previousValue, element) {
|
||||
return previousValue + element.duration;
|
||||
});
|
||||
final total = book.tracks.sublist(0, index).fold<Duration>(
|
||||
Duration.zero,
|
||||
(previousValue, element) => previousValue + element.duration,
|
||||
);
|
||||
_logger.fine('Sum of tracks for index: $index is $total');
|
||||
return total;
|
||||
}
|
||||
|
||||
/// returns the [AudioTrack] to play based on the [position] in the [book]
|
||||
AudioTrack getTrackToPlay(BookExpanded book, Duration position) {
|
||||
// var totalDuration = Duration.zero;
|
||||
// for (var track in book.tracks) {
|
||||
// totalDuration += track.duration;
|
||||
// if (totalDuration >= position) {
|
||||
// return track;
|
||||
// }
|
||||
// }
|
||||
// return book.tracks.last;
|
||||
return book.tracks.firstWhere(
|
||||
_logger.fine('Getting track to play for position: $position');
|
||||
final track = book.tracks.firstWhere(
|
||||
(element) {
|
||||
return element.startOffset <= position &&
|
||||
(element.startOffset + element.duration) >= position;
|
||||
},
|
||||
orElse: () => book.tracks.last,
|
||||
);
|
||||
_logger.fine('Track to play for position: $position is $track');
|
||||
return track;
|
||||
}
|
||||
|
||||
/// will manage the audio player instance
|
||||
|
|
@ -50,6 +50,7 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
// constructor which takes in the BookExpanded object
|
||||
AudiobookPlayer(this.token, this.baseUrl) : super() {
|
||||
// set the source of the player to the first track in the book
|
||||
_logger.config('Setting up audiobook player');
|
||||
}
|
||||
|
||||
/// the [BookExpanded] being played
|
||||
|
|
@ -84,20 +85,23 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
List<Uri>? downloadedUris,
|
||||
Uri? artworkUri,
|
||||
}) async {
|
||||
_logger.finer(
|
||||
'Initial position: $initialPosition, Downloaded URIs: $downloadedUris',
|
||||
);
|
||||
final appSettings = loadOrCreateAppSettings();
|
||||
// if the book is null, stop the player
|
||||
if (book == null) {
|
||||
_book = null;
|
||||
_logger.info('Book is null, stopping player');
|
||||
return stop();
|
||||
}
|
||||
|
||||
// see if the book is the same as the current book
|
||||
if (_book == book) {
|
||||
_logger.info('Book is the same, doing nothing');
|
||||
return;
|
||||
}
|
||||
// first stop the player and clear the source
|
||||
_logger.info('Setting source for book: $book');
|
||||
|
||||
_logger.fine('Stopping player');
|
||||
await stop();
|
||||
|
||||
_book = book;
|
||||
|
|
@ -114,6 +118,7 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
? initialPosition - trackToPlay.startOffset
|
||||
: null;
|
||||
|
||||
_logger.finer('Setting audioSource');
|
||||
await setAudioSource(
|
||||
preload: preload,
|
||||
initialIndex: initialIndex,
|
||||
|
|
@ -124,7 +129,7 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
final retrievedUri =
|
||||
_getUri(track, downloadedUris, baseUrl: baseUrl, token: token);
|
||||
_logger.fine(
|
||||
'Setting source for track: ${track.title}, URI: $retrievedUri',
|
||||
'Setting source for track: ${track.title}, URI: ${retrievedUri.obfuscate()}',
|
||||
);
|
||||
return AudioSource.uri(
|
||||
retrievedUri,
|
||||
|
|
@ -145,7 +150,7 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
}).toList(),
|
||||
),
|
||||
).catchError((error) {
|
||||
_logger.shout('Error: $error');
|
||||
_logger.shout('Error in setting audio source: $error');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +158,7 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
Future<void> togglePlayPause() {
|
||||
// check if book is set
|
||||
if (_book == null) {
|
||||
throw StateError('No book is set');
|
||||
_logger.warning('No book is set, not toggling play/pause');
|
||||
}
|
||||
|
||||
// TODO refactor this to cover all the states
|
||||
|
|
@ -169,9 +174,11 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
@override
|
||||
Future<void> seek(Duration? positionInBook, {int? index}) async {
|
||||
if (_book == null) {
|
||||
_logger.warning('No book is set, not seeking');
|
||||
return;
|
||||
}
|
||||
if (positionInBook == null) {
|
||||
_logger.warning('Position given is null, not seeking');
|
||||
return;
|
||||
}
|
||||
final tracks = _book!.tracks;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,11 @@
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:vaani/api/api_provider.dart';
|
||||
import 'package:vaani/features/player/core/audiobook_player.dart'
|
||||
as core;
|
||||
import 'package:vaani/features/player/core/audiobook_player.dart' as core;
|
||||
|
||||
part 'audiobook_player.g.dart';
|
||||
|
||||
// @Riverpod(keepAlive: true)
|
||||
// core.AudiobookPlayer audiobookPlayer(
|
||||
// AudiobookPlayerRef ref,
|
||||
// ) {
|
||||
// final api = ref.watch(authenticatedApiProvider);
|
||||
// final player = core.AudiobookPlayer(api.token!, api.baseUrl);
|
||||
|
||||
// ref.onDispose(player.dispose);
|
||||
|
||||
// return player;
|
||||
// }
|
||||
final _logger = Logger('AudiobookPlayerProvider');
|
||||
|
||||
const playerId = 'audiobook_player';
|
||||
|
||||
|
|
@ -32,6 +22,7 @@ class SimpleAudiobookPlayer extends _$SimpleAudiobookPlayer {
|
|||
);
|
||||
|
||||
ref.onDispose(player.dispose);
|
||||
_logger.finer('created simple player');
|
||||
|
||||
return player;
|
||||
}
|
||||
|
|
@ -47,18 +38,16 @@ class AudiobookPlayer extends _$AudiobookPlayer {
|
|||
|
||||
// bind notify listeners to the player
|
||||
player.playerStateStream.listen((_) {
|
||||
notifyListeners();
|
||||
ref.notifyListeners();
|
||||
});
|
||||
|
||||
_logger.finer('created player');
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
void notifyListeners() {
|
||||
ref.notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setSpeed(double speed) async {
|
||||
await state.setSpeed(speed);
|
||||
notifyListeners();
|
||||
ref.notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'audiobook_player.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$simpleAudiobookPlayerHash() =>
|
||||
r'9e11ed2791d35e308f8cbe61a79a45cf51466ebb';
|
||||
r'5e94bbff4314adceb5affa704fc4d079d4016afa';
|
||||
|
||||
/// Simple because it doesn't rebuild when the player state changes
|
||||
/// it only rebuilds when the token changes
|
||||
|
|
@ -26,7 +26,7 @@ final simpleAudiobookPlayerProvider =
|
|||
);
|
||||
|
||||
typedef _$SimpleAudiobookPlayer = Notifier<core.AudiobookPlayer>;
|
||||
String _$audiobookPlayerHash() => r'44394b1dbbf85eb19ef1f693717e8cbc15b768e5';
|
||||
String _$audiobookPlayerHash() => r'0f180308067486896fec6a65a6afb0e6686ac4a0';
|
||||
|
||||
/// See also [AudiobookPlayer].
|
||||
@ProviderFor(AudiobookPlayer)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:vaani/features/player/providers/audiobook_player.dart';
|
||||
|
|
@ -5,12 +6,15 @@ import 'package:vaani/shared/extensions/model_conversions.dart';
|
|||
|
||||
part 'currently_playing_provider.g.dart';
|
||||
|
||||
final _logger = Logger('CurrentlyPlayingProvider');
|
||||
|
||||
@riverpod
|
||||
BookExpanded? currentlyPlayingBook(CurrentlyPlayingBookRef ref) {
|
||||
try {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
return player.book;
|
||||
} catch (e) {
|
||||
_logger.warning('Error getting currently playing book: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'currently_playing_provider.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$currentlyPlayingBookHash() =>
|
||||
r'52334c7b4d68fd498a2a00208d8d7f1ba0085237';
|
||||
r'7440b0d54cb364f66e704783652e8f1490ae90e0';
|
||||
|
||||
/// See also [currentlyPlayingBook].
|
||||
@ProviderFor(currentlyPlayingBook)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||
import 'package:vaani/features/player/providers/audiobook_player.dart';
|
||||
import 'package:vaani/features/player/providers/currently_playing_provider.dart';
|
||||
import 'package:vaani/features/player/view/player_when_expanded.dart';
|
||||
import 'package:vaani/main.dart';
|
||||
import 'package:vaani/shared/extensions/chapter.dart';
|
||||
import 'package:vaani/shared/extensions/duration_format.dart';
|
||||
import 'package:vaani/shared/hooks.dart';
|
||||
|
|
@ -55,7 +56,7 @@ class ChapterSelectionModal extends HookConsumerWidget {
|
|||
final currentChapterIndex = currentChapter?.id;
|
||||
final chapterKey = GlobalKey();
|
||||
scrollToCurrentChapter() async {
|
||||
debugPrint('scrolling to chapter');
|
||||
appLogger.fine('scrolling to chapter');
|
||||
await Scrollable.ensureVisible(
|
||||
chapterKey.currentContext!,
|
||||
duration: 200.ms,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue