mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-02-16 14:29:35 +00:00
一堆乱七八糟的修改
播放页面增加桌面版
This commit is contained in:
parent
aee1fbde88
commit
3ba35b31b8
116 changed files with 1238 additions and 2592 deletions
|
|
@ -1,71 +1,112 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio_media_kit/just_audio_media_kit.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart' as shelfsdk;
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart' as core;
|
||||
import 'package:vaani/api/api_provider.dart';
|
||||
import 'package:vaani/features/player/core/audiobook_player.dart' as core;
|
||||
import 'package:vaani/api/library_item_provider.dart';
|
||||
import 'package:vaani/features/downloads/providers/download_manager.dart';
|
||||
import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart';
|
||||
import 'package:vaani/features/player/core/audiobook_player.dart';
|
||||
import 'package:vaani/features/player/providers/player_status_provider.dart';
|
||||
import 'package:vaani/features/settings/app_settings_provider.dart';
|
||||
import 'package:vaani/shared/extensions/model_conversions.dart';
|
||||
import 'package:vaani/shared/utils/helper.dart';
|
||||
|
||||
part 'audiobook_player.g.dart';
|
||||
|
||||
final _logger = Logger('AudiobookPlayerProvider');
|
||||
|
||||
const playerId = 'audiobook_player';
|
||||
|
||||
/// Simple because it doesn't rebuild when the player state changes
|
||||
/// it only rebuilds when the token changes
|
||||
@Riverpod(keepAlive: true)
|
||||
class SimpleAudiobookPlayer extends _$SimpleAudiobookPlayer {
|
||||
Future<AbsAudioHandler> audioHandlerInit(Ref ref) async {
|
||||
if (Helper.isWindows() || Helper.isLinux()) {
|
||||
// JustAudioMediaKit.ensureInitialized(windows: false);
|
||||
JustAudioMediaKit.ensureInitialized();
|
||||
}
|
||||
|
||||
final audioService = await AudioService.init(
|
||||
builder: () => AbsAudioHandler(ref),
|
||||
config: const AudioServiceConfig(
|
||||
androidNotificationChannelId: 'dr.blank.vaani.channel.audio',
|
||||
androidNotificationChannelName: 'ABSPlayback',
|
||||
androidNotificationChannelDescription:
|
||||
'Needed to control audio from lock screen',
|
||||
androidNotificationOngoing: false,
|
||||
androidStopForegroundOnPause: false,
|
||||
androidNotificationIcon: 'drawable/ic_stat_logo',
|
||||
preloadArtwork: true,
|
||||
),
|
||||
);
|
||||
return audioService;
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class Player extends _$Player {
|
||||
@override
|
||||
core.AudiobookPlayer build() {
|
||||
final api = ref.watch(authenticatedApiProvider);
|
||||
final player = core.AudiobookPlayer(
|
||||
api.token!,
|
||||
api.baseUrl,
|
||||
);
|
||||
|
||||
ref.onDispose(player.dispose);
|
||||
_logger.finer('created simple player');
|
||||
|
||||
return player;
|
||||
AbsAudioHandler build() {
|
||||
return ref.watch(audioHandlerInitProvider).requireValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class AudiobookPlayer extends _$AudiobookPlayer {
|
||||
class Session extends _$Session {
|
||||
@override
|
||||
core.AudiobookPlayer build() {
|
||||
final player = ref.watch(simpleAudiobookPlayerProvider);
|
||||
|
||||
ref.onDispose(player.dispose);
|
||||
|
||||
// bind notify listeners to the player
|
||||
// player.playerStateStream.listen((_) {
|
||||
// ref.notifyListeners();
|
||||
// });
|
||||
|
||||
_logger.finer('created player');
|
||||
|
||||
return player;
|
||||
core.PlaybackSessionExpanded? build() {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> setSpeed(double speed) async {
|
||||
await state.setSpeed(speed);
|
||||
ref.notifyListeners();
|
||||
}
|
||||
Future<void> load(String id, String? episodeId) async {
|
||||
final audioService = ref.read(playerProvider);
|
||||
await audioService.pause();
|
||||
ref.read(playerStatusProvider.notifier).setLoading(id);
|
||||
final api = ref.read(authenticatedApiProvider);
|
||||
final playBack = await ref.watch(playBackSessionProvider(id).future);
|
||||
if (playBack == null) {
|
||||
return;
|
||||
}
|
||||
state = playBack.asExpanded;
|
||||
final downloadManager = ref.read(simpleDownloadManagerProvider);
|
||||
final libItem =
|
||||
await ref.read(libraryItemProvider(state!.libraryItemId).future);
|
||||
final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem);
|
||||
|
||||
Future<void> setSourceAudiobook({
|
||||
required shelfsdk.BookExpanded book,
|
||||
shelfsdk.MediaProgress? userMediaProgress,
|
||||
}) async {
|
||||
ref.notifyListeners();
|
||||
var bookPlayerSettings =
|
||||
ref.read(bookSettingsProvider(state!.libraryItemId)).playerSettings;
|
||||
var appPlayerSettings = ref.read(appSettingsProvider).playerSettings;
|
||||
|
||||
var configurePlayerForEveryBook =
|
||||
appPlayerSettings.configurePlayerForEveryBook;
|
||||
|
||||
await Future.wait([
|
||||
audioService.setSourceAudiobook(
|
||||
state!.asExpanded,
|
||||
baseUrl: api.baseUrl,
|
||||
token: api.token!,
|
||||
downloadedUris: downloadedUris,
|
||||
),
|
||||
// set the volume
|
||||
audioService.setVolume(
|
||||
configurePlayerForEveryBook
|
||||
? bookPlayerSettings.preferredDefaultVolume ??
|
||||
appPlayerSettings.preferredDefaultVolume
|
||||
: appPlayerSettings.preferredDefaultVolume,
|
||||
),
|
||||
// set the speed
|
||||
audioService.setSpeed(
|
||||
configurePlayerForEveryBook
|
||||
? bookPlayerSettings.preferredDefaultSpeed ??
|
||||
appPlayerSettings.preferredDefaultSpeed
|
||||
: appPlayerSettings.preferredDefaultSpeed,
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
bool isPlayerPlaying(
|
||||
Ref ref,
|
||||
) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
print("playing: ${player.playing}");
|
||||
return player.playing;
|
||||
class PlaybackSyncError implements Exception {
|
||||
String message;
|
||||
|
||||
PlaybackSyncError([this.message = 'Error syncing playback']);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlaybackSyncError: $message';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,58 +6,51 @@ part of 'audiobook_player.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$isPlayerPlayingHash() => r'b81fa9cfb51c88c8d9e8f5c1f4f6a12d9e5a0cc1';
|
||||
String _$audioHandlerInitHash() => r'6e4662a45c1c6e84aa16436f71ffcfecc3d4bdab';
|
||||
|
||||
/// See also [isPlayerPlaying].
|
||||
@ProviderFor(isPlayerPlaying)
|
||||
final isPlayerPlayingProvider = AutoDisposeProvider<bool>.internal(
|
||||
isPlayerPlaying,
|
||||
name: r'isPlayerPlayingProvider',
|
||||
/// See also [audioHandlerInit].
|
||||
@ProviderFor(audioHandlerInit)
|
||||
final audioHandlerInitProvider = FutureProvider<AbsAudioHandler>.internal(
|
||||
audioHandlerInit,
|
||||
name: r'audioHandlerInitProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$isPlayerPlayingHash,
|
||||
: _$audioHandlerInitHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef IsPlayerPlayingRef = AutoDisposeProviderRef<bool>;
|
||||
String _$simpleAudiobookPlayerHash() =>
|
||||
r'5e94bbff4314adceb5affa704fc4d079d4016afa';
|
||||
typedef AudioHandlerInitRef = FutureProviderRef<AbsAudioHandler>;
|
||||
String _$playerHash() => r'9599b094cdd9eca614c27ec5bdf2d5259d20ac5f';
|
||||
|
||||
/// Simple because it doesn't rebuild when the player state changes
|
||||
/// it only rebuilds when the token changes
|
||||
///
|
||||
/// Copied from [SimpleAudiobookPlayer].
|
||||
@ProviderFor(SimpleAudiobookPlayer)
|
||||
final simpleAudiobookPlayerProvider =
|
||||
NotifierProvider<SimpleAudiobookPlayer, core.AudiobookPlayer>.internal(
|
||||
SimpleAudiobookPlayer.new,
|
||||
name: r'simpleAudiobookPlayerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$simpleAudiobookPlayerHash,
|
||||
/// See also [Player].
|
||||
@ProviderFor(Player)
|
||||
final playerProvider = NotifierProvider<Player, AbsAudioHandler>.internal(
|
||||
Player.new,
|
||||
name: r'playerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$playerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SimpleAudiobookPlayer = Notifier<core.AudiobookPlayer>;
|
||||
String _$audiobookPlayerHash() => r'04448247e79c5d60b9fd6f98eeeb865f1e8d0ff8';
|
||||
typedef _$Player = Notifier<AbsAudioHandler>;
|
||||
String _$sessionHash() => r'c171809249c3021dc445dc1ba90fe8626a3d3b54';
|
||||
|
||||
/// See also [AudiobookPlayer].
|
||||
@ProviderFor(AudiobookPlayer)
|
||||
final audiobookPlayerProvider =
|
||||
NotifierProvider<AudiobookPlayer, core.AudiobookPlayer>.internal(
|
||||
AudiobookPlayer.new,
|
||||
name: r'audiobookPlayerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$audiobookPlayerHash,
|
||||
/// See also [Session].
|
||||
@ProviderFor(Session)
|
||||
final sessionProvider =
|
||||
NotifierProvider<Session, core.PlaybackSessionExpanded?>.internal(
|
||||
Session.new,
|
||||
name: r'sessionProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$sessionHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$AudiobookPlayer = Notifier<core.AudiobookPlayer>;
|
||||
typedef _$Session = Notifier<core.PlaybackSessionExpanded?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
|
|
|||
|
|
@ -1,46 +1,40 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart' as core;
|
||||
import 'package:vaani/features/player/providers/audiobook_player.dart';
|
||||
import 'package:vaani/shared/extensions/model_conversions.dart';
|
||||
|
||||
part 'currently_playing_provider.g.dart';
|
||||
|
||||
final _logger = Logger('CurrentlyPlayingProvider');
|
||||
|
||||
@riverpod
|
||||
BookExpanded? currentlyPlayingBook(Ref ref) {
|
||||
try {
|
||||
final book = ref.watch(simpleAudiobookPlayerProvider.select((v) => v.book));
|
||||
return book;
|
||||
} catch (e) {
|
||||
_logger.warning('Error getting currently playing book: $e');
|
||||
return null;
|
||||
class CurrentChapter extends _$CurrentChapter {
|
||||
@override
|
||||
core.BookChapter? build() {
|
||||
final player = ref.watch(playerProvider);
|
||||
player.chapterStream.distinct().listen((chapter) {
|
||||
update(chapter);
|
||||
});
|
||||
return player.currentChapter;
|
||||
}
|
||||
|
||||
void update(core.BookChapter? chapter) {
|
||||
if (state != chapter) {
|
||||
state = chapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// provided the current chapter of the book being played
|
||||
@riverpod
|
||||
BookChapter? currentPlayingChapter(Ref ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
player.slowPositionStreamInBook.listen((_) {
|
||||
ref.invalidateSelf();
|
||||
});
|
||||
|
||||
return player.currentChapter;
|
||||
List<core.BookChapter> currentChapters(Ref ref) {
|
||||
final session = ref.watch(sessionProvider);
|
||||
if (session == null) {
|
||||
return [];
|
||||
}
|
||||
final currentChapter = ref.watch(currentChapterProvider);
|
||||
if (currentChapter == null) {
|
||||
return [];
|
||||
}
|
||||
final index = session.chapters.indexOf(currentChapter);
|
||||
final total = session.chapters.length;
|
||||
return session.chapters
|
||||
.sublist(index - 3, (total - 3) <= (index + 17) ? total : index + 17);
|
||||
}
|
||||
|
||||
/// provides the book metadata of the currently playing book
|
||||
@riverpod
|
||||
BookMetadataExpanded? currentBookMetadata(Ref ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
if (player.book == null) return null;
|
||||
return player.book!.metadata.asBookMetadataExpanded;
|
||||
}
|
||||
|
||||
// /// volume of the player [0, 1]
|
||||
// @riverpod
|
||||
// double currentVolume(CurrentVolumeRef ref) {
|
||||
// return 1;
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -6,66 +6,39 @@ part of 'currently_playing_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$currentlyPlayingBookHash() =>
|
||||
r'f2c47028340d253be9440dc29f835328ff30c0e6';
|
||||
String _$currentChaptersHash() => r'f2cc6ec31b5a3a9471775b1c96b2bfc3a91f1c90';
|
||||
|
||||
/// See also [currentlyPlayingBook].
|
||||
@ProviderFor(currentlyPlayingBook)
|
||||
final currentlyPlayingBookProvider =
|
||||
AutoDisposeProvider<BookExpanded?>.internal(
|
||||
currentlyPlayingBook,
|
||||
name: r'currentlyPlayingBookProvider',
|
||||
/// See also [currentChapters].
|
||||
@ProviderFor(currentChapters)
|
||||
final currentChaptersProvider =
|
||||
AutoDisposeProvider<List<core.BookChapter>>.internal(
|
||||
currentChapters,
|
||||
name: r'currentChaptersProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$currentlyPlayingBookHash,
|
||||
: _$currentChaptersHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef CurrentlyPlayingBookRef = AutoDisposeProviderRef<BookExpanded?>;
|
||||
String _$currentPlayingChapterHash() =>
|
||||
r'4a64157089279c71279ccfdbcfc7b32543ecc88c';
|
||||
typedef CurrentChaptersRef = AutoDisposeProviderRef<List<core.BookChapter>>;
|
||||
String _$currentChapterHash() => r'f5f6d9e49cb7e455d032f7370f364d9ce30b8eb1';
|
||||
|
||||
/// provided the current chapter of the book being played
|
||||
///
|
||||
/// Copied from [currentPlayingChapter].
|
||||
@ProviderFor(currentPlayingChapter)
|
||||
final currentPlayingChapterProvider =
|
||||
AutoDisposeProvider<BookChapter?>.internal(
|
||||
currentPlayingChapter,
|
||||
name: r'currentPlayingChapterProvider',
|
||||
/// See also [CurrentChapter].
|
||||
@ProviderFor(CurrentChapter)
|
||||
final currentChapterProvider =
|
||||
AutoDisposeNotifierProvider<CurrentChapter, core.BookChapter?>.internal(
|
||||
CurrentChapter.new,
|
||||
name: r'currentChapterProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$currentPlayingChapterHash,
|
||||
: _$currentChapterHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef CurrentPlayingChapterRef = AutoDisposeProviderRef<BookChapter?>;
|
||||
String _$currentBookMetadataHash() =>
|
||||
r'f537ef4ef19280bc952de658ecf6520c535ae344';
|
||||
|
||||
/// provides the book metadata of the currently playing book
|
||||
///
|
||||
/// Copied from [currentBookMetadata].
|
||||
@ProviderFor(currentBookMetadata)
|
||||
final currentBookMetadataProvider =
|
||||
AutoDisposeProvider<BookMetadataExpanded?>.internal(
|
||||
currentBookMetadata,
|
||||
name: r'currentBookMetadataProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$currentBookMetadataHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef CurrentBookMetadataRef = AutoDisposeProviderRef<BookMetadataExpanded?>;
|
||||
typedef _$CurrentChapter = AutoDisposeNotifier<core.BookChapter?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
// this provider is used to manage the player form state
|
||||
// it will inform about the percentage of the player expanded
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
// import 'package:miniplayer/miniplayer.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:vaani/features/player/providers/audiobook_player.dart';
|
||||
|
||||
part 'player_form.g.dart';
|
||||
|
||||
/// The height of the player when it is minimized
|
||||
const double playerMinHeight = 70;
|
||||
// const miniplayerPercentageDeclaration = 0.2;
|
||||
|
||||
extension on Ref {
|
||||
// We can move the previous logic to a Ref extension.
|
||||
// This enables reusing the logic between providers
|
||||
T disposeAndListenChangeNotifier<T extends ChangeNotifier>(T notifier) {
|
||||
onDispose(notifier.dispose);
|
||||
notifier.addListener(notifyListeners);
|
||||
// We return the notifier to ease the usage a bit
|
||||
return notifier;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Raw<ValueNotifier<double>> playerExpandProgressNotifier(
|
||||
Ref ref,
|
||||
) {
|
||||
final ValueNotifier<double> playerExpandProgress =
|
||||
ValueNotifier(playerMinHeight);
|
||||
|
||||
return ref.disposeAndListenChangeNotifier(playerExpandProgress);
|
||||
}
|
||||
|
||||
// @Riverpod(keepAlive: true)
|
||||
// Raw<ValueNotifier<double>> dragDownPercentageNotifier(
|
||||
// DragDownPercentageNotifierRef ref,
|
||||
// ) {
|
||||
// final ValueNotifier<double> notifier = ValueNotifier(0);
|
||||
|
||||
// return ref.disposeAndListenChangeNotifier(notifier);
|
||||
// }
|
||||
|
||||
// a provider that will listen to the playerExpandProgressNotifier and return the percentage of the player expanded
|
||||
@Riverpod(keepAlive: true)
|
||||
double playerHeight(
|
||||
Ref ref,
|
||||
) {
|
||||
final playerExpandProgress = ref.watch(playerExpandProgressNotifierProvider);
|
||||
|
||||
// on change of the playerExpandProgress invalidate
|
||||
playerExpandProgress.addListener(() {
|
||||
ref.invalidateSelf();
|
||||
});
|
||||
|
||||
// listen to the playerExpandProgressNotifier and return the value
|
||||
return playerExpandProgress.value;
|
||||
}
|
||||
|
||||
// final audioBookMiniplayerController = MiniplayerController();
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
bool isPlayerActive(
|
||||
Ref ref,
|
||||
) {
|
||||
try {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
if (player.book != null) {
|
||||
return true;
|
||||
} else {
|
||||
final playerHeight = ref.watch(playerHeightProvider);
|
||||
return playerHeight < playerMinHeight;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'player_form.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$playerExpandProgressNotifierHash() =>
|
||||
r'1ac7172d90a070f96222286edd1a176be197f378';
|
||||
|
||||
/// See also [playerExpandProgressNotifier].
|
||||
@ProviderFor(playerExpandProgressNotifier)
|
||||
final playerExpandProgressNotifierProvider =
|
||||
Provider<Raw<ValueNotifier<double>>>.internal(
|
||||
playerExpandProgressNotifier,
|
||||
name: r'playerExpandProgressNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$playerExpandProgressNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef PlayerExpandProgressNotifierRef
|
||||
= ProviderRef<Raw<ValueNotifier<double>>>;
|
||||
String _$playerHeightHash() => r'3f031eaffdffbb2c6ddf7eb1aba31bf1619260fc';
|
||||
|
||||
/// See also [playerHeight].
|
||||
@ProviderFor(playerHeight)
|
||||
final playerHeightProvider = Provider<double>.internal(
|
||||
playerHeight,
|
||||
name: r'playerHeightProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$playerHeightHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef PlayerHeightRef = ProviderRef<double>;
|
||||
String _$isPlayerActiveHash() => r'2c7ca125423126fb5f0ef218d37bc8fe0ca9ec98';
|
||||
|
||||
/// See also [isPlayerActive].
|
||||
@ProviderFor(isPlayerActive)
|
||||
final isPlayerActiveProvider = Provider<bool>.internal(
|
||||
isPlayerActive,
|
||||
name: r'isPlayerActiveProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$isPlayerActiveHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef IsPlayerActiveRef = ProviderRef<bool>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:just_audio_media_kit/just_audio_media_kit.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart' as core;
|
||||
import 'package:vaani/api/api_provider.dart';
|
||||
import 'package:vaani/api/library_item_provider.dart';
|
||||
import 'package:vaani/features/downloads/providers/download_manager.dart';
|
||||
import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart';
|
||||
import 'package:vaani/features/playback_reporting/core/playback_reporter_session.dart'
|
||||
as core;
|
||||
import 'package:vaani/features/player/core/audiobook_player_session.dart';
|
||||
import 'package:vaani/features/player/providers/player_status_provider.dart';
|
||||
import 'package:vaani/globals.dart';
|
||||
import 'package:vaani/settings/app_settings_provider.dart';
|
||||
import 'package:vaani/shared/extensions/obfuscation.dart';
|
||||
import 'package:vaani/shared/utils/utils.dart';
|
||||
|
||||
part 'session_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<AbsAudioHandler> audioHandlerInit(Ref ref) async {
|
||||
if (Utils.isWindows() || Utils.isLinux()) {
|
||||
// JustAudioMediaKit.ensureInitialized(windows: false);
|
||||
JustAudioMediaKit.ensureInitialized();
|
||||
}
|
||||
|
||||
final audioService = await AudioService.init(
|
||||
builder: () => AbsAudioHandler(ref),
|
||||
config: const AudioServiceConfig(
|
||||
androidNotificationChannelId: 'com.vaani.rang.channel.audio',
|
||||
androidNotificationChannelName: 'ABSPlayback',
|
||||
androidNotificationChannelDescription:
|
||||
'Needed to control audio from lock screen',
|
||||
androidNotificationOngoing: false,
|
||||
androidStopForegroundOnPause: false,
|
||||
androidNotificationIcon: 'drawable/ic_stat_logo',
|
||||
preloadArtwork: true,
|
||||
),
|
||||
);
|
||||
return audioService;
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class Player extends _$Player {
|
||||
@override
|
||||
AbsAudioHandler build() {
|
||||
return ref.watch(audioHandlerInitProvider).requireValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class Session extends _$Session {
|
||||
@override
|
||||
core.PlaybackSessionExpanded? build() {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> load(String id, String? episodeId) async {
|
||||
final audioService = ref.read(playerProvider);
|
||||
await audioService.pause();
|
||||
ref.read(playerStatusProvider.notifier).setLoading(id);
|
||||
final api = ref.read(authenticatedApiProvider);
|
||||
final playBack = await api.items.play(
|
||||
libraryItemId: id,
|
||||
parameters: core.PlayItemReqParams(
|
||||
deviceInfo: core.DeviceInfoReqParams(
|
||||
clientVersion: appVersion,
|
||||
manufacturer: deviceManufacturer,
|
||||
model: deviceModel,
|
||||
sdkVersion: deviceSdkVersion,
|
||||
clientName: appName,
|
||||
deviceName: deviceName,
|
||||
),
|
||||
forceDirectPlay: false,
|
||||
forceTranscode: false,
|
||||
supportedMimeTypes: [
|
||||
"audio/flac",
|
||||
"audio/mpeg",
|
||||
"audio/mp4",
|
||||
"audio/ogg",
|
||||
"audio/aac",
|
||||
"audio/webm",
|
||||
],
|
||||
),
|
||||
responseErrorHandler: _responseErrorHandler,
|
||||
) as core.PlaybackSessionExpanded;
|
||||
state = playBack;
|
||||
final downloadManager = ref.read(simpleDownloadManagerProvider);
|
||||
final libItem =
|
||||
await ref.read(libraryItemProvider(playBack.libraryItemId).future);
|
||||
final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem);
|
||||
|
||||
var bookPlayerSettings =
|
||||
ref.read(bookSettingsProvider(playBack.libraryItemId)).playerSettings;
|
||||
var appPlayerSettings = ref.read(appSettingsProvider).playerSettings;
|
||||
|
||||
var configurePlayerForEveryBook =
|
||||
appPlayerSettings.configurePlayerForEveryBook;
|
||||
|
||||
await Future.wait([
|
||||
audioService.setSourceAudiobook(
|
||||
playBack,
|
||||
baseUrl: api.baseUrl,
|
||||
token: api.token!,
|
||||
downloadedUris: downloadedUris,
|
||||
),
|
||||
// set the volume
|
||||
audioService.setVolume(
|
||||
configurePlayerForEveryBook
|
||||
? bookPlayerSettings.preferredDefaultVolume ??
|
||||
appPlayerSettings.preferredDefaultVolume
|
||||
: appPlayerSettings.preferredDefaultVolume,
|
||||
),
|
||||
// set the speed
|
||||
audioService.setSpeed(
|
||||
configurePlayerForEveryBook
|
||||
? bookPlayerSettings.preferredDefaultSpeed ??
|
||||
appPlayerSettings.preferredDefaultSpeed
|
||||
: appPlayerSettings.preferredDefaultSpeed,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
void _responseErrorHandler(http.Response response, [error]) {
|
||||
if (response.statusCode != 200) {
|
||||
appLogger.severe('Error with api: ${response.obfuscate()}, $error');
|
||||
throw PlaybackSyncError(
|
||||
'Error syncing position: ${response.body}, $error',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class CurrentChapter extends _$CurrentChapter {
|
||||
@override
|
||||
core.BookChapter? build() {
|
||||
final player = ref.watch(playerProvider);
|
||||
player.chapterStream.distinct().listen((chapter) {
|
||||
update(chapter);
|
||||
});
|
||||
return player.currentChapter;
|
||||
}
|
||||
|
||||
void update(core.BookChapter? chapter) {
|
||||
if (state != chapter) {
|
||||
state = chapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class PlaybackReporter extends _$PlaybackReporter {
|
||||
@override
|
||||
Future<core.PlaybackReporter?> build() async {
|
||||
final session = ref.watch(sessionProvider);
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
final playerSettings = ref.watch(appSettingsProvider).playerSettings;
|
||||
final player = ref.watch(playerProvider);
|
||||
final api = ref.watch(authenticatedApiProvider);
|
||||
|
||||
final reporter = core.PlaybackReporter(
|
||||
player,
|
||||
api,
|
||||
reportingInterval: playerSettings.playbackReportInterval,
|
||||
markCompleteWhenTimeLeft: playerSettings.markCompleteWhenTimeLeft,
|
||||
minimumPositionForReporting: playerSettings.minimumPositionForReporting,
|
||||
session: session,
|
||||
);
|
||||
ref.onDispose(reporter.dispose);
|
||||
return reporter;
|
||||
}
|
||||
}
|
||||
|
||||
class PlaybackSyncError implements Exception {
|
||||
String message;
|
||||
|
||||
PlaybackSyncError([this.message = 'Error syncing playback']);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlaybackSyncError: $message';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'session_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$audioHandlerInitHash() => r'c54f17757807f8bc14daff5095c34eb88ff2037b';
|
||||
|
||||
/// See also [audioHandlerInit].
|
||||
@ProviderFor(audioHandlerInit)
|
||||
final audioHandlerInitProvider = FutureProvider<AbsAudioHandler>.internal(
|
||||
audioHandlerInit,
|
||||
name: r'audioHandlerInitProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$audioHandlerInitHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef AudioHandlerInitRef = FutureProviderRef<AbsAudioHandler>;
|
||||
String _$playerHash() => r'9599b094cdd9eca614c27ec5bdf2d5259d20ac5f';
|
||||
|
||||
/// See also [Player].
|
||||
@ProviderFor(Player)
|
||||
final playerProvider = NotifierProvider<Player, AbsAudioHandler>.internal(
|
||||
Player.new,
|
||||
name: r'playerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$playerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$Player = Notifier<AbsAudioHandler>;
|
||||
String _$sessionHash() => r'ce9405cc0b8247014924a005fa60fbc3bf92d5c6';
|
||||
|
||||
/// See also [Session].
|
||||
@ProviderFor(Session)
|
||||
final sessionProvider =
|
||||
NotifierProvider<Session, core.PlaybackSessionExpanded?>.internal(
|
||||
Session.new,
|
||||
name: r'sessionProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$sessionHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$Session = Notifier<core.PlaybackSessionExpanded?>;
|
||||
String _$currentChapterHash() => r'0be02fbc4f65b30da4ce18964ee457a18c4d1073';
|
||||
|
||||
/// See also [CurrentChapter].
|
||||
@ProviderFor(CurrentChapter)
|
||||
final currentChapterProvider =
|
||||
NotifierProvider<CurrentChapter, core.BookChapter?>.internal(
|
||||
CurrentChapter.new,
|
||||
name: r'currentChapterProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$currentChapterHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$CurrentChapter = Notifier<core.BookChapter?>;
|
||||
String _$playbackReporterHash() => r'b9a2197a12e83f9760bd61ae2a1ad8dff82042e9';
|
||||
|
||||
/// See also [PlaybackReporter].
|
||||
@ProviderFor(PlaybackReporter)
|
||||
final playbackReporterProvider =
|
||||
AsyncNotifierProvider<PlaybackReporter, core.PlaybackReporter?>.internal(
|
||||
PlaybackReporter.new,
|
||||
name: r'playbackReporterProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$playbackReporterHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$PlaybackReporter = AsyncNotifier<core.PlaybackReporter?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
Loading…
Add table
Add a link
Reference in a new issue