一堆乱七八糟的修改

播放页面增加桌面版
This commit is contained in:
rang 2025-11-28 17:05:35 +08:00
parent aee1fbde88
commit 3ba35b31b8
116 changed files with 1238 additions and 2592 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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