Skip to content

Commit

Permalink
feat: new settings to configure when to report playback and when to m…
Browse files Browse the repository at this point in the history
…ark item complete (#32)
  • Loading branch information
Dr-Blank authored Sep 26, 2024
1 parent 7279fa0 commit 8049a66
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 7 deletions.
61 changes: 58 additions & 3 deletions lib/features/playback_reporting/core/playback_reporter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ class PlaybackReporter {
/// the minimum duration to report
final Duration reportingDurationThreshold;

/// the duration to wait before starting the reporting
/// this is to ignore the initial duration in case user is browsing
final Duration? minimumPositionForReporting;

/// the duration to mark the book as complete when the time left is less than this
final Duration markCompleteWhenTimeLeft;

/// timer to report every 10 seconds
/// tracking the time since the last report
Timer? _reportTimer;

/// metadata to report
Expand All @@ -61,6 +69,8 @@ class PlaybackReporter {
this.deviceManufacturer,
this.reportingDurationThreshold = const Duration(seconds: 1),
Duration reportingInterval = const Duration(seconds: 10),
this.minimumPositionForReporting,
this.markCompleteWhenTimeLeft = const Duration(seconds: 5),
}) : _reportingInterval = reportingInterval {
// initial conditions
if (player.playing) {
Expand Down Expand Up @@ -97,23 +107,35 @@ class PlaybackReporter {
_logger.fine(
'player state observed, stopping stopwatch at ${_stopwatch.elapsed}',
);
await syncCurrentPosition();
await tryReportPlayback(null);
}
}),
);

_logger.fine(
'initialized with interval: $reportingInterval, threshold: $reportingDurationThreshold',
'initialized with reportingInterval: $reportingInterval, reportingDurationThreshold: $reportingDurationThreshold',
);
_logger.fine(
'initialized with minimumPositionForReporting: $minimumPositionForReporting, markCompleteWhenTimeLeft: $markCompleteWhenTimeLeft',
);
_logger.fine(
'initialized with deviceModel: $deviceModel, deviceSdkVersion: $deviceSdkVersion, deviceClientName: $deviceClientName, deviceClientVersion: $deviceClientVersion, deviceManufacturer: $deviceManufacturer',
);
}

void tryReportPlayback(_) async {
Future<void> tryReportPlayback(_) async {
_logger.fine(
'callback called when elapsed ${_stopwatch.elapsed}',
);
if (player.book != null &&
player.positionInBook >=
player.book!.duration - markCompleteWhenTimeLeft) {
_logger.info(
'marking complete as time left is less than $markCompleteWhenTimeLeft',
);
await markComplete();
return;
}
if (_stopwatch.elapsed > reportingDurationThreshold) {
_logger.fine(
'reporting now with elapsed ${_stopwatch.elapsed} > threshold $reportingDurationThreshold',
Expand Down Expand Up @@ -159,6 +181,22 @@ class PlaybackReporter {
return _session;
}

Future<void> markComplete() async {
if (player.book == null) {
throw NoAudiobookPlayingError();
}
await authenticatedApi.me.createUpdateMediaProgress(
libraryItemId: player.book!.libraryItemId,
parameters: CreateUpdateProgressReqParams(
isFinished: true,
currentTime: player.positionInBook,
duration: player.book!.duration,
),
responseErrorHandler: _responseErrorHandler,
);
_logger.info('Marked complete for book: ${player.book!.libraryItemId}');
}

Future<void> syncCurrentPosition() async {
final data = _getSyncData();
if (data == null) {
Expand Down Expand Up @@ -229,6 +267,23 @@ class PlaybackReporter {
);
return null;
}

// if in the ignore duration, don't sync
if (minimumPositionForReporting != null &&
player.positionInBook < minimumPositionForReporting!) {
// but if elapsed time is more than the minimumPositionForReporting, sync
if (_stopwatch.elapsed > minimumPositionForReporting!) {
_logger.info(
'Syncing position despite being less than minimumPositionForReporting as elapsed time is more: ${_stopwatch.elapsed}',
);
} else {
_logger.info(
'Ignoring sync for position: ${player.positionInBook} < $minimumPositionForReporting',
);
return null;
}
}

return SyncSessionReqParams(
currentTime: player.positionInBook,
timeListened: _stopwatch.elapsed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ part 'playback_reporter_provider.g.dart';
class PlaybackReporter extends _$PlaybackReporter {
@override
Future<core.PlaybackReporter> build() async {
final appSettings = ref.watch(appSettingsProvider);
final playerSettings = ref.watch(appSettingsProvider).playerSettings;
final player = ref.watch(simpleAudiobookPlayerProvider);
final packageInfo = await PackageInfo.fromPlatform();
final api = ref.watch(authenticatedApiProvider);
Expand All @@ -26,7 +26,9 @@ class PlaybackReporter extends _$PlaybackReporter {
final reporter = core.PlaybackReporter(
player,
api,
reportingInterval: appSettings.playerSettings.playbackReportInterval,
reportingInterval: playerSettings.playbackReportInterval,
markCompleteWhenTimeLeft: playerSettings.markCompleteWhenTimeLeft,
minimumPositionForReporting: playerSettings.minimumPositionForReporting,
deviceName: deviceName,
deviceModel: deviceModel,
deviceSdkVersion: deviceSdkVersion,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lib/settings/models/app_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ class PlayerSettings with _$PlayerSettings {
@Default(1) double preferredDefaultSpeed,
@Default([0.75, 1, 1.25, 1.5, 1.75, 2]) List<double> speedOptions,
@Default(SleepTimerSettings()) SleepTimerSettings sleepTimerSettings,
@Default(Duration(seconds: 10)) Duration minimumPositionForReporting,
@Default(Duration(seconds: 10)) Duration playbackReportInterval,
@Default(Duration(seconds: 15)) Duration markCompleteWhenTimeLeft,
@Default(true) bool configurePlayerForEveryBook,
}) = _PlayerSettings;

Expand Down
52 changes: 51 additions & 1 deletion lib/settings/models/app_settings.freezed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,10 @@ mixin _$PlayerSettings {
List<double> get speedOptions => throw _privateConstructorUsedError;
SleepTimerSettings get sleepTimerSettings =>
throw _privateConstructorUsedError;
Duration get minimumPositionForReporting =>
throw _privateConstructorUsedError;
Duration get playbackReportInterval => throw _privateConstructorUsedError;
Duration get markCompleteWhenTimeLeft => throw _privateConstructorUsedError;
bool get configurePlayerForEveryBook => throw _privateConstructorUsedError;

/// Serializes this PlayerSettings to a JSON map.
Expand All @@ -540,7 +543,9 @@ abstract class $PlayerSettingsCopyWith<$Res> {
double preferredDefaultSpeed,
List<double> speedOptions,
SleepTimerSettings sleepTimerSettings,
Duration minimumPositionForReporting,
Duration playbackReportInterval,
Duration markCompleteWhenTimeLeft,
bool configurePlayerForEveryBook});

$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
Expand Down Expand Up @@ -569,7 +574,9 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
Object? preferredDefaultSpeed = null,
Object? speedOptions = null,
Object? sleepTimerSettings = null,
Object? minimumPositionForReporting = null,
Object? playbackReportInterval = null,
Object? markCompleteWhenTimeLeft = null,
Object? configurePlayerForEveryBook = null,
}) {
return _then(_value.copyWith(
Expand Down Expand Up @@ -597,10 +604,18 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
? _value.sleepTimerSettings
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
as SleepTimerSettings,
minimumPositionForReporting: null == minimumPositionForReporting
? _value.minimumPositionForReporting
: minimumPositionForReporting // ignore: cast_nullable_to_non_nullable
as Duration,
playbackReportInterval: null == playbackReportInterval
? _value.playbackReportInterval
: playbackReportInterval // ignore: cast_nullable_to_non_nullable
as Duration,
markCompleteWhenTimeLeft: null == markCompleteWhenTimeLeft
? _value.markCompleteWhenTimeLeft
: markCompleteWhenTimeLeft // ignore: cast_nullable_to_non_nullable
as Duration,
configurePlayerForEveryBook: null == configurePlayerForEveryBook
? _value.configurePlayerForEveryBook
: configurePlayerForEveryBook // ignore: cast_nullable_to_non_nullable
Expand Down Expand Up @@ -657,7 +672,9 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res>
double preferredDefaultSpeed,
List<double> speedOptions,
SleepTimerSettings sleepTimerSettings,
Duration minimumPositionForReporting,
Duration playbackReportInterval,
Duration markCompleteWhenTimeLeft,
bool configurePlayerForEveryBook});

@override
Expand Down Expand Up @@ -687,7 +704,9 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
Object? preferredDefaultSpeed = null,
Object? speedOptions = null,
Object? sleepTimerSettings = null,
Object? minimumPositionForReporting = null,
Object? playbackReportInterval = null,
Object? markCompleteWhenTimeLeft = null,
Object? configurePlayerForEveryBook = null,
}) {
return _then(_$PlayerSettingsImpl(
Expand Down Expand Up @@ -715,10 +734,18 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
? _value.sleepTimerSettings
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
as SleepTimerSettings,
minimumPositionForReporting: null == minimumPositionForReporting
? _value.minimumPositionForReporting
: minimumPositionForReporting // ignore: cast_nullable_to_non_nullable
as Duration,
playbackReportInterval: null == playbackReportInterval
? _value.playbackReportInterval
: playbackReportInterval // ignore: cast_nullable_to_non_nullable
as Duration,
markCompleteWhenTimeLeft: null == markCompleteWhenTimeLeft
? _value.markCompleteWhenTimeLeft
: markCompleteWhenTimeLeft // ignore: cast_nullable_to_non_nullable
as Duration,
configurePlayerForEveryBook: null == configurePlayerForEveryBook
? _value.configurePlayerForEveryBook
: configurePlayerForEveryBook // ignore: cast_nullable_to_non_nullable
Expand All @@ -737,7 +764,9 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
this.preferredDefaultSpeed = 1,
final List<double> speedOptions = const [0.75, 1, 1.25, 1.5, 1.75, 2],
this.sleepTimerSettings = const SleepTimerSettings(),
this.minimumPositionForReporting = const Duration(seconds: 10),
this.playbackReportInterval = const Duration(seconds: 10),
this.markCompleteWhenTimeLeft = const Duration(seconds: 15),
this.configurePlayerForEveryBook = true})
: _speedOptions = speedOptions;

Expand Down Expand Up @@ -770,14 +799,20 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
final SleepTimerSettings sleepTimerSettings;
@override
@JsonKey()
final Duration minimumPositionForReporting;
@override
@JsonKey()
final Duration playbackReportInterval;
@override
@JsonKey()
final Duration markCompleteWhenTimeLeft;
@override
@JsonKey()
final bool configurePlayerForEveryBook;

@override
String toString() {
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimerSettings: $sleepTimerSettings, playbackReportInterval: $playbackReportInterval, configurePlayerForEveryBook: $configurePlayerForEveryBook)';
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimerSettings: $sleepTimerSettings, minimumPositionForReporting: $minimumPositionForReporting, playbackReportInterval: $playbackReportInterval, markCompleteWhenTimeLeft: $markCompleteWhenTimeLeft, configurePlayerForEveryBook: $configurePlayerForEveryBook)';
}

@override
Expand All @@ -797,8 +832,15 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
.equals(other._speedOptions, _speedOptions) &&
(identical(other.sleepTimerSettings, sleepTimerSettings) ||
other.sleepTimerSettings == sleepTimerSettings) &&
(identical(other.minimumPositionForReporting,
minimumPositionForReporting) ||
other.minimumPositionForReporting ==
minimumPositionForReporting) &&
(identical(other.playbackReportInterval, playbackReportInterval) ||
other.playbackReportInterval == playbackReportInterval) &&
(identical(
other.markCompleteWhenTimeLeft, markCompleteWhenTimeLeft) ||
other.markCompleteWhenTimeLeft == markCompleteWhenTimeLeft) &&
(identical(other.configurePlayerForEveryBook,
configurePlayerForEveryBook) ||
other.configurePlayerForEveryBook ==
Expand All @@ -815,7 +857,9 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
preferredDefaultSpeed,
const DeepCollectionEquality().hash(_speedOptions),
sleepTimerSettings,
minimumPositionForReporting,
playbackReportInterval,
markCompleteWhenTimeLeft,
configurePlayerForEveryBook);

/// Create a copy of PlayerSettings
Expand Down Expand Up @@ -843,7 +887,9 @@ abstract class _PlayerSettings implements PlayerSettings {
final double preferredDefaultSpeed,
final List<double> speedOptions,
final SleepTimerSettings sleepTimerSettings,
final Duration minimumPositionForReporting,
final Duration playbackReportInterval,
final Duration markCompleteWhenTimeLeft,
final bool configurePlayerForEveryBook}) = _$PlayerSettingsImpl;

factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
Expand All @@ -862,8 +908,12 @@ abstract class _PlayerSettings implements PlayerSettings {
@override
SleepTimerSettings get sleepTimerSettings;
@override
Duration get minimumPositionForReporting;
@override
Duration get playbackReportInterval;
@override
Duration get markCompleteWhenTimeLeft;
@override
bool get configurePlayerForEveryBook;

/// Create a copy of PlayerSettings
Expand Down
13 changes: 13 additions & 0 deletions lib/settings/models/app_settings.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8049a66

Please sign in to comment.