mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-06 02:59:28 +00:00
playback reporting
This commit is contained in:
parent
fbd789f989
commit
be7f5daa88
14 changed files with 751 additions and 10 deletions
252
lib/features/playback_reporting/core/playback_reporter.dart
Normal file
252
lib/features/playback_reporting/core/playback_reporter.dart
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:whispering_pages/features/player/core/audiobook_player.dart';
|
||||
|
||||
/// this playback reporter will watch the player and report to the server
|
||||
///
|
||||
/// it will by default report every 10 seconds
|
||||
/// and also report when the player is paused/stopped/finished/playing
|
||||
class PlaybackReporter {
|
||||
/// The player to watch
|
||||
final AudiobookPlayer player;
|
||||
|
||||
/// the api to report to
|
||||
final AudiobookshelfApi authenticatedApi;
|
||||
|
||||
/// The stopwatch to keep track of the time since the last report
|
||||
final _stopwatch = Stopwatch();
|
||||
|
||||
/// subscriptions
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
|
||||
Duration _reportingInterval;
|
||||
|
||||
/// the duration to wait before reporting
|
||||
Duration get reportingInterval => _reportingInterval;
|
||||
set reportingInterval(Duration value) {
|
||||
_reportingInterval = value;
|
||||
_cancelReportTimer();
|
||||
_setReportTimer();
|
||||
debugPrint('PlaybackReporter set interval: $value');
|
||||
}
|
||||
|
||||
/// the minimum duration to report
|
||||
final Duration reportingDurationThreshold;
|
||||
|
||||
/// timer to report every 10 seconds
|
||||
Timer? _reportTimer;
|
||||
|
||||
/// metadata to report
|
||||
String? deviceName;
|
||||
String? deviceModel;
|
||||
String? deviceSdkVersion;
|
||||
String? deviceClientName;
|
||||
String? deviceClientVersion;
|
||||
String? deviceManufacturer;
|
||||
|
||||
PlaybackReporter(
|
||||
this.player,
|
||||
this.authenticatedApi, {
|
||||
this.deviceName,
|
||||
this.deviceModel,
|
||||
this.deviceSdkVersion,
|
||||
this.deviceClientName,
|
||||
this.deviceClientVersion,
|
||||
this.deviceManufacturer,
|
||||
this.reportingDurationThreshold = const Duration(seconds: 1),
|
||||
Duration reportingInterval = const Duration(seconds: 10),
|
||||
}) : _reportingInterval = reportingInterval {
|
||||
// initial conditions
|
||||
if (player.playing) {
|
||||
_stopwatch.start();
|
||||
_setReportTimer();
|
||||
debugPrint('PlaybackReporter starting stopwatch');
|
||||
} else {
|
||||
debugPrint('PlaybackReporter not starting stopwatch');
|
||||
}
|
||||
|
||||
_subscriptions.add(
|
||||
player.playerStateStream.listen((state) async {
|
||||
// set timer if any book is playing and cancel if not
|
||||
if (player.book != null && _reportTimer == null) {
|
||||
_setReportTimer();
|
||||
} else if (player.book == null && _reportTimer != null) {
|
||||
debugPrint('PlaybackReporter book is null, closing session');
|
||||
await closeSession();
|
||||
_cancelReportTimer();
|
||||
}
|
||||
|
||||
// start or stop the stopwatch based on the playing state
|
||||
if (state.playing) {
|
||||
_stopwatch.start();
|
||||
debugPrint(
|
||||
'PlaybackReporter player state observed, starting stopwatch at ${_stopwatch.elapsed}',
|
||||
);
|
||||
} else if (!state.playing) {
|
||||
_stopwatch.stop();
|
||||
debugPrint(
|
||||
'PlaybackReporter player state observed, stopping stopwatch at ${_stopwatch.elapsed}',
|
||||
);
|
||||
await syncCurrentPosition();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
debugPrint(
|
||||
'PlaybackReporter initialized with interval: $reportingInterval, threshold: $reportingDurationThreshold',
|
||||
);
|
||||
debugPrint(
|
||||
'PlaybackReporter initialized with deviceModel: $deviceModel, deviceSdkVersion: $deviceSdkVersion, deviceClientName: $deviceClientName, deviceClientVersion: $deviceClientVersion, deviceManufacturer: $deviceManufacturer',
|
||||
);
|
||||
}
|
||||
|
||||
void tryReportPlayback(_) async {
|
||||
debugPrint(
|
||||
'PlaybackReporter callback called when elapsed ${_stopwatch.elapsed}',
|
||||
);
|
||||
if (_stopwatch.elapsed > reportingDurationThreshold) {
|
||||
debugPrint(
|
||||
'PlaybackReporter reporting now with elapsed ${_stopwatch.elapsed} > threshold $reportingDurationThreshold',
|
||||
);
|
||||
await syncCurrentPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// dispose the timer
|
||||
Future<void> dispose() async {
|
||||
for (var sub in _subscriptions) {
|
||||
sub.cancel();
|
||||
}
|
||||
await closeSession();
|
||||
_stopwatch.stop();
|
||||
_reportTimer?.cancel();
|
||||
|
||||
debugPrint('PlaybackReporter disposed');
|
||||
}
|
||||
|
||||
/// current sessionId
|
||||
/// this is used to report the playback
|
||||
String? sessionId;
|
||||
|
||||
Future<String?> startSession() async {
|
||||
if (sessionId != null) {
|
||||
return sessionId!;
|
||||
}
|
||||
if (player.book == null) {
|
||||
throw NoAudiobookPlayingError();
|
||||
}
|
||||
final session = await authenticatedApi.items.play(
|
||||
libraryItemId: player.book!.libraryItemId,
|
||||
parameters: PlayItemReqParams(
|
||||
deviceInfo: await _getDeviceInfo(),
|
||||
forceDirectPlay: false,
|
||||
forceTranscode: false,
|
||||
),
|
||||
responseErrorHandler: _responseErrorHandler,
|
||||
);
|
||||
sessionId = session!.id;
|
||||
debugPrint('PlaybackReporter Started session: $sessionId');
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
Future<void> syncCurrentPosition() async {
|
||||
try {
|
||||
sessionId ??= await startSession();
|
||||
} on NoAudiobookPlayingError {
|
||||
debugPrint('PlaybackReporter No audiobook playing to sync position');
|
||||
return;
|
||||
}
|
||||
final currentPosition = player.position;
|
||||
|
||||
await authenticatedApi.sessions.syncOpen(
|
||||
sessionId: sessionId!,
|
||||
parameters: _getSyncData(),
|
||||
responseErrorHandler: _responseErrorHandler,
|
||||
);
|
||||
|
||||
debugPrint(
|
||||
'PlaybackReporter Synced position: $currentPosition with timeListened: ${_stopwatch.elapsed} for session: $sessionId',
|
||||
);
|
||||
|
||||
// reset the stopwatch
|
||||
_stopwatch.reset();
|
||||
}
|
||||
|
||||
Future<void> closeSession() async {
|
||||
if (sessionId == null) {
|
||||
debugPrint('PlaybackReporter No session to close');
|
||||
return;
|
||||
}
|
||||
|
||||
await authenticatedApi.sessions.closeOpen(
|
||||
sessionId: sessionId!,
|
||||
parameters: _getSyncData(),
|
||||
responseErrorHandler: _responseErrorHandler,
|
||||
);
|
||||
sessionId = null;
|
||||
debugPrint('PlaybackReporter Closed session');
|
||||
}
|
||||
|
||||
void _setReportTimer() {
|
||||
_reportTimer = Timer.periodic(_reportingInterval, tryReportPlayback);
|
||||
debugPrint('PlaybackReporter set timer with interval: $_reportingInterval');
|
||||
}
|
||||
|
||||
void _cancelReportTimer() {
|
||||
_reportTimer?.cancel();
|
||||
_reportTimer = null;
|
||||
debugPrint('PlaybackReporter cancelled timer');
|
||||
}
|
||||
|
||||
void _responseErrorHandler(response, [error]) {
|
||||
if (response.statusCode != 200) {
|
||||
debugPrint('PlaybackReporter Error with api: $response, $error');
|
||||
throw PlaybackSyncError(
|
||||
'Error syncing position: ${response.body}, $error',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SyncSessionReqParams _getSyncData() {
|
||||
return SyncSessionReqParams(
|
||||
currentTime: player.position,
|
||||
timeListened: _stopwatch.elapsed,
|
||||
duration: player.book?.duration ?? Duration.zero,
|
||||
);
|
||||
}
|
||||
|
||||
Future<DeviceInfoReqParams> _getDeviceInfo() async {
|
||||
return DeviceInfoReqParams(
|
||||
clientVersion: deviceClientVersion,
|
||||
manufacturer: deviceManufacturer,
|
||||
model: deviceModel,
|
||||
sdkVersion: deviceSdkVersion,
|
||||
clientName: deviceClientName,
|
||||
deviceName: deviceName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PlaybackSyncError implements Exception {
|
||||
String message;
|
||||
|
||||
PlaybackSyncError([this.message = 'Error syncing playback']);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlaybackSyncError: $message';
|
||||
}
|
||||
}
|
||||
|
||||
class NoAudiobookPlayingError implements Exception {
|
||||
String message;
|
||||
|
||||
NoAudiobookPlayingError([this.message = 'No audiobook is playing']);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NoAudiobookPlayingError: $message';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:whispering_pages/api/api_provider.dart';
|
||||
import 'package:whispering_pages/features/playback_reporting/core/playback_reporter.dart'
|
||||
as core;
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||
import 'package:whispering_pages/settings/metadata/metadata_provider.dart';
|
||||
|
||||
part 'playback_reporter_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class PlaybackReporter extends _$PlaybackReporter {
|
||||
@override
|
||||
Future<core.PlaybackReporter> build() async {
|
||||
final appSettings = ref.watch(appSettingsProvider);
|
||||
final player = ref.watch(simpleAudiobookPlayerProvider);
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final api = ref.watch(authenticatedApiProvider);
|
||||
final deviceName = await ref.watch(deviceNameProvider.future);
|
||||
final deviceModel = await ref.watch(deviceModelProvider.future);
|
||||
final deviceSdkVersion = await ref.watch(deviceSdkVersionProvider.future);
|
||||
final deviceManufacturer =
|
||||
await ref.watch(deviceManufacturerProvider.future);
|
||||
|
||||
final reporter = core.PlaybackReporter(
|
||||
player,
|
||||
api,
|
||||
reportingInterval: appSettings.playerSettings.playbackReportInterval,
|
||||
deviceName: deviceName,
|
||||
deviceModel: deviceModel,
|
||||
deviceSdkVersion: deviceSdkVersion,
|
||||
deviceClientName: packageInfo.appName,
|
||||
deviceClientVersion: packageInfo.version,
|
||||
deviceManufacturer: deviceManufacturer,
|
||||
);
|
||||
ref.onDispose(reporter.dispose);
|
||||
return reporter;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'playback_reporter_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$playbackReporterHash() => r'c210b7286d9c151fd59a9ead9eb4a28d1cffdc7c';
|
||||
|
||||
/// 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
|
||||
|
|
@ -19,6 +19,8 @@ part 'audiobook_player.g.dart';
|
|||
|
||||
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 {
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ part of 'audiobook_player.dart';
|
|||
String _$simpleAudiobookPlayerHash() =>
|
||||
r'b65e6d779476a2c1fa38f617771bf997acb4f5b8';
|
||||
|
||||
/// See also [SimpleAudiobookPlayer].
|
||||
/// 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, abp.AudiobookPlayer>.internal(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:just_audio_media_kit/just_audio_media_kit.dart'
|
|||
show JustAudioMediaKit;
|
||||
import 'package:whispering_pages/api/server_provider.dart';
|
||||
import 'package:whispering_pages/db/storage.dart';
|
||||
import 'package:whispering_pages/features/playback_reporting/providers/playback_reporter_provider.dart';
|
||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||
import 'package:whispering_pages/features/sleep_timer/providers/sleep_timer_provider.dart';
|
||||
import 'package:whispering_pages/router/router.dart';
|
||||
|
|
@ -85,9 +86,11 @@ class _EagerInitialization extends ConsumerWidget {
|
|||
try {
|
||||
ref.watch(simpleAudiobookPlayerProvider);
|
||||
ref.watch(sleepTimerProvider);
|
||||
ref.watch(playbackReporterProvider);
|
||||
} catch (e) {
|
||||
debugPrintStack(stackTrace: StackTrace.current, label: e.toString());
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
259
lib/settings/metadata/metadata_provider.dart
Normal file
259
lib/settings/metadata/metadata_provider.dart
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'metadata_provider.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<String> deviceName(DeviceNameRef ref) async {
|
||||
final data = await _getDeviceData(DeviceInfoPlugin());
|
||||
|
||||
// try different keys to get the device name
|
||||
return
|
||||
// android
|
||||
data['product'] ??
|
||||
// ios
|
||||
data['name'] ??
|
||||
// linux
|
||||
data['name'] ??
|
||||
// windows
|
||||
data['computerName'] ??
|
||||
// macos
|
||||
data['model'] ??
|
||||
// web
|
||||
data['browserName'] ??
|
||||
'Unknown name';
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<String> deviceModel(DeviceModelRef ref) async {
|
||||
final data = await _getDeviceData(DeviceInfoPlugin());
|
||||
|
||||
// try different keys to get the device model
|
||||
return
|
||||
// android, eg: Google Pixel 4
|
||||
data['model'] ??
|
||||
// ios, eg: iPhone 12 Pro
|
||||
data['name'] ??
|
||||
// linux, eg: Linux Mint 20.1
|
||||
data['name'] ??
|
||||
// windows, eg: Surface Pro 7
|
||||
data['productId'] ??
|
||||
// macos, eg: MacBook Pro (13-inch, M1, 2020)
|
||||
data['model'] ??
|
||||
// web, eg: Chrome 87.0.4280.88
|
||||
data['browserName'] ??
|
||||
'Unknown model';
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<String> deviceSdkVersion(DeviceSdkVersionRef ref) async {
|
||||
final data = await _getDeviceData(DeviceInfoPlugin());
|
||||
|
||||
// try different keys to get the device sdk version
|
||||
return
|
||||
// android, eg: 30
|
||||
data['version.sdkInt'] ??
|
||||
// ios, eg: 14.4
|
||||
data['systemVersion'] ??
|
||||
// linux, eg: 5.4.0-66-generic
|
||||
data['version'] ??
|
||||
// windows, eg: 10.0.19042
|
||||
data['displayVersion'] ??
|
||||
// macos, eg: 11.2.1
|
||||
data['osRelease'] ??
|
||||
// web, eg: 87.0.4280.88
|
||||
data['appVersion'] ??
|
||||
'Unknown sdk version';
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<String> deviceManufacturer(DeviceManufacturerRef ref) async {
|
||||
final data = await _getDeviceData(DeviceInfoPlugin());
|
||||
|
||||
// try different keys to get the device manufacturer
|
||||
return
|
||||
// android, eg: Google
|
||||
data['manufacturer'] ??
|
||||
// ios, eg: Apple
|
||||
data['manufacturer'] ??
|
||||
// linux, eg: Linux
|
||||
data['idLike'] ??
|
||||
// windows, eg: Microsoft
|
||||
data['productName'] ??
|
||||
// macos, eg: Apple
|
||||
data['manufacturer'] ??
|
||||
// web, eg: Google Inc.
|
||||
data['vendor'] ??
|
||||
'Unknown manufacturer';
|
||||
}
|
||||
|
||||
// copied from https://pub.dev/packages/device_info_plus/example
|
||||
Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
|
||||
return <String, dynamic>{
|
||||
'version.securityPatch': build.version.securityPatch,
|
||||
'version.sdkInt': build.version.sdkInt,
|
||||
'version.release': build.version.release,
|
||||
'version.previewSdkInt': build.version.previewSdkInt,
|
||||
'version.incremental': build.version.incremental,
|
||||
'version.codename': build.version.codename,
|
||||
'version.baseOS': build.version.baseOS,
|
||||
'board': build.board,
|
||||
'bootloader': build.bootloader,
|
||||
'brand': build.brand,
|
||||
'device': build.device,
|
||||
'display': build.display,
|
||||
'fingerprint': build.fingerprint,
|
||||
'hardware': build.hardware,
|
||||
'host': build.host,
|
||||
'id': build.id,
|
||||
'manufacturer': build.manufacturer,
|
||||
'model': build.model,
|
||||
'product': build.product,
|
||||
'supported32BitAbis': build.supported32BitAbis,
|
||||
'supported64BitAbis': build.supported64BitAbis,
|
||||
'supportedAbis': build.supportedAbis,
|
||||
'tags': build.tags,
|
||||
'type': build.type,
|
||||
'isPhysicalDevice': build.isPhysicalDevice,
|
||||
'systemFeatures': build.systemFeatures,
|
||||
'serialNumber': build.serialNumber,
|
||||
'isLowRamDevice': build.isLowRamDevice,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readIosDeviceInfo(IosDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'name': data.name,
|
||||
'systemName': data.systemName,
|
||||
'systemVersion': data.systemVersion,
|
||||
'model': data.model,
|
||||
'localizedModel': data.localizedModel,
|
||||
'identifierForVendor': data.identifierForVendor,
|
||||
'isPhysicalDevice': data.isPhysicalDevice,
|
||||
'utsname.sysname:': data.utsname.sysname,
|
||||
'utsname.nodename:': data.utsname.nodename,
|
||||
'utsname.release:': data.utsname.release,
|
||||
'utsname.version:': data.utsname.version,
|
||||
'utsname.machine:': data.utsname.machine,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readLinuxDeviceInfo(LinuxDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'name': data.name,
|
||||
'version': data.version,
|
||||
'id': data.id,
|
||||
'idLike': data.idLike,
|
||||
'versionCodename': data.versionCodename,
|
||||
'versionId': data.versionId,
|
||||
'prettyName': data.prettyName,
|
||||
'buildId': data.buildId,
|
||||
'variant': data.variant,
|
||||
'variantId': data.variantId,
|
||||
'machineId': data.machineId,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readWebBrowserInfo(WebBrowserInfo data) {
|
||||
return <String, dynamic>{
|
||||
'browserName': data.browserName.name,
|
||||
'appCodeName': data.appCodeName,
|
||||
'appName': data.appName,
|
||||
'appVersion': data.appVersion,
|
||||
'deviceMemory': data.deviceMemory,
|
||||
'language': data.language,
|
||||
'languages': data.languages,
|
||||
'platform': data.platform,
|
||||
'product': data.product,
|
||||
'productSub': data.productSub,
|
||||
'userAgent': data.userAgent,
|
||||
'vendor': data.vendor,
|
||||
'vendorSub': data.vendorSub,
|
||||
'hardwareConcurrency': data.hardwareConcurrency,
|
||||
'maxTouchPoints': data.maxTouchPoints,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readMacOsDeviceInfo(MacOsDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'computerName': data.computerName,
|
||||
'hostName': data.hostName,
|
||||
'arch': data.arch,
|
||||
'model': data.model,
|
||||
'kernelVersion': data.kernelVersion,
|
||||
'majorVersion': data.majorVersion,
|
||||
'minorVersion': data.minorVersion,
|
||||
'patchVersion': data.patchVersion,
|
||||
'osRelease': data.osRelease,
|
||||
'activeCPUs': data.activeCPUs,
|
||||
'memorySize': data.memorySize,
|
||||
'cpuFrequency': data.cpuFrequency,
|
||||
'systemGUID': data.systemGUID,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readWindowsDeviceInfo(WindowsDeviceInfo data) {
|
||||
return <String, dynamic>{
|
||||
'numberOfCores': data.numberOfCores,
|
||||
'computerName': data.computerName,
|
||||
'systemMemoryInMegabytes': data.systemMemoryInMegabytes,
|
||||
'userName': data.userName,
|
||||
'majorVersion': data.majorVersion,
|
||||
'minorVersion': data.minorVersion,
|
||||
'buildNumber': data.buildNumber,
|
||||
'platformId': data.platformId,
|
||||
'csdVersion': data.csdVersion,
|
||||
'servicePackMajor': data.servicePackMajor,
|
||||
'servicePackMinor': data.servicePackMinor,
|
||||
'suitMask': data.suitMask,
|
||||
'productType': data.productType,
|
||||
'reserved': data.reserved,
|
||||
'buildLab': data.buildLab,
|
||||
'buildLabEx': data.buildLabEx,
|
||||
'digitalProductId': data.digitalProductId,
|
||||
'displayVersion': data.displayVersion,
|
||||
'editionId': data.editionId,
|
||||
'installDate': data.installDate,
|
||||
'productId': data.productId,
|
||||
'productName': data.productName,
|
||||
'registeredOwner': data.registeredOwner,
|
||||
'releaseId': data.releaseId,
|
||||
'deviceId': data.deviceId,
|
||||
};
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _getDeviceData(
|
||||
DeviceInfoPlugin deviceInfoPlugin,
|
||||
) async {
|
||||
Map<String, dynamic> deviceData;
|
||||
try {
|
||||
if (kIsWeb) {
|
||||
deviceData = _readWebBrowserInfo(await deviceInfoPlugin.webBrowserInfo);
|
||||
} else {
|
||||
deviceData = switch (defaultTargetPlatform) {
|
||||
TargetPlatform.android =>
|
||||
_readAndroidBuildData(await deviceInfoPlugin.androidInfo),
|
||||
TargetPlatform.iOS =>
|
||||
_readIosDeviceInfo(await deviceInfoPlugin.iosInfo),
|
||||
TargetPlatform.linux =>
|
||||
_readLinuxDeviceInfo(await deviceInfoPlugin.linuxInfo),
|
||||
TargetPlatform.windows =>
|
||||
_readWindowsDeviceInfo(await deviceInfoPlugin.windowsInfo),
|
||||
TargetPlatform.macOS =>
|
||||
_readMacOsDeviceInfo(await deviceInfoPlugin.macOsInfo),
|
||||
TargetPlatform.fuchsia => <String, dynamic>{
|
||||
errorKey: 'Fuchsia platform isn\'t supported',
|
||||
},
|
||||
};
|
||||
}
|
||||
} on PlatformException {
|
||||
deviceData = <String, dynamic>{
|
||||
errorKey: 'Failed to get platform version.',
|
||||
};
|
||||
}
|
||||
return deviceData;
|
||||
}
|
||||
|
||||
const errorKey = 'Error:';
|
||||
69
lib/settings/metadata/metadata_provider.g.dart
Normal file
69
lib/settings/metadata/metadata_provider.g.dart
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'metadata_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$deviceNameHash() => r'bc206a3a8c14f3da6e257e92e1ccdc79364f4e28';
|
||||
|
||||
/// See also [deviceName].
|
||||
@ProviderFor(deviceName)
|
||||
final deviceNameProvider = FutureProvider<String>.internal(
|
||||
deviceName,
|
||||
name: r'deviceNameProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$deviceNameHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef DeviceNameRef = FutureProviderRef<String>;
|
||||
String _$deviceModelHash() => r'3d7e8ef4a37b90f98e38dc8d5f16ca30f71e15b2';
|
||||
|
||||
/// See also [deviceModel].
|
||||
@ProviderFor(deviceModel)
|
||||
final deviceModelProvider = FutureProvider<String>.internal(
|
||||
deviceModel,
|
||||
name: r'deviceModelProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$deviceModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef DeviceModelRef = FutureProviderRef<String>;
|
||||
String _$deviceSdkVersionHash() => r'0553db1a6c90a4db2841761ac2765eb1ba86a714';
|
||||
|
||||
/// See also [deviceSdkVersion].
|
||||
@ProviderFor(deviceSdkVersion)
|
||||
final deviceSdkVersionProvider = FutureProvider<String>.internal(
|
||||
deviceSdkVersion,
|
||||
name: r'deviceSdkVersionProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$deviceSdkVersionHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef DeviceSdkVersionRef = FutureProviderRef<String>;
|
||||
String _$deviceManufacturerHash() =>
|
||||
r'f0a57e6a92b551fbe266d0a6a29d35dc497882a9';
|
||||
|
||||
/// See also [deviceManufacturer].
|
||||
@ProviderFor(deviceManufacturer)
|
||||
final deviceManufacturerProvider = FutureProvider<String>.internal(
|
||||
deviceManufacturer,
|
||||
name: r'deviceManufacturerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$deviceManufacturerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef DeviceManufacturerRef = FutureProviderRef<String>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
|
@ -31,6 +31,7 @@ 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 playbackReportInterval,
|
||||
}) = _PlayerSettings;
|
||||
|
||||
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ mixin _$PlayerSettings {
|
|||
List<double> get speedOptions => throw _privateConstructorUsedError;
|
||||
SleepTimerSettings get sleepTimerSettings =>
|
||||
throw _privateConstructorUsedError;
|
||||
Duration get playbackReportInterval => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
|
|
@ -250,7 +251,8 @@ abstract class $PlayerSettingsCopyWith<$Res> {
|
|||
double preferredDefaultVolume,
|
||||
double preferredDefaultSpeed,
|
||||
List<double> speedOptions,
|
||||
SleepTimerSettings sleepTimerSettings});
|
||||
SleepTimerSettings sleepTimerSettings,
|
||||
Duration playbackReportInterval});
|
||||
|
||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||
$ExpandedPlayerSettingsCopyWith<$Res> get expandedPlayerSettings;
|
||||
|
|
@ -276,6 +278,7 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
Object? preferredDefaultSpeed = null,
|
||||
Object? speedOptions = null,
|
||||
Object? sleepTimerSettings = null,
|
||||
Object? playbackReportInterval = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
miniPlayerSettings: null == miniPlayerSettings
|
||||
|
|
@ -302,6 +305,10 @@ class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
|||
? _value.sleepTimerSettings
|
||||
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
|
||||
as SleepTimerSettings,
|
||||
playbackReportInterval: null == playbackReportInterval
|
||||
? _value.playbackReportInterval
|
||||
: playbackReportInterval // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
|
|
@ -347,7 +354,8 @@ abstract class _$$PlayerSettingsImplCopyWith<$Res>
|
|||
double preferredDefaultVolume,
|
||||
double preferredDefaultSpeed,
|
||||
List<double> speedOptions,
|
||||
SleepTimerSettings sleepTimerSettings});
|
||||
SleepTimerSettings sleepTimerSettings,
|
||||
Duration playbackReportInterval});
|
||||
|
||||
@override
|
||||
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||
|
|
@ -374,6 +382,7 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
Object? preferredDefaultSpeed = null,
|
||||
Object? speedOptions = null,
|
||||
Object? sleepTimerSettings = null,
|
||||
Object? playbackReportInterval = null,
|
||||
}) {
|
||||
return _then(_$PlayerSettingsImpl(
|
||||
miniPlayerSettings: null == miniPlayerSettings
|
||||
|
|
@ -400,6 +409,10 @@ class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
|||
? _value.sleepTimerSettings
|
||||
: sleepTimerSettings // ignore: cast_nullable_to_non_nullable
|
||||
as SleepTimerSettings,
|
||||
playbackReportInterval: null == playbackReportInterval
|
||||
? _value.playbackReportInterval
|
||||
: playbackReportInterval // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -413,7 +426,8 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
this.preferredDefaultVolume = 1,
|
||||
this.preferredDefaultSpeed = 1,
|
||||
final List<double> speedOptions = const [0.75, 1, 1.25, 1.5, 1.75, 2],
|
||||
this.sleepTimerSettings = const SleepTimerSettings()})
|
||||
this.sleepTimerSettings = const SleepTimerSettings(),
|
||||
this.playbackReportInterval = const Duration(seconds: 10)})
|
||||
: _speedOptions = speedOptions;
|
||||
|
||||
factory _$PlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
|
|
@ -443,10 +457,13 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
@override
|
||||
@JsonKey()
|
||||
final SleepTimerSettings sleepTimerSettings;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Duration playbackReportInterval;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimerSettings: $sleepTimerSettings)';
|
||||
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings, expandedPlayerSettings: $expandedPlayerSettings, preferredDefaultVolume: $preferredDefaultVolume, preferredDefaultSpeed: $preferredDefaultSpeed, speedOptions: $speedOptions, sleepTimerSettings: $sleepTimerSettings, playbackReportInterval: $playbackReportInterval)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -465,7 +482,9 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
const DeepCollectionEquality()
|
||||
.equals(other._speedOptions, _speedOptions) &&
|
||||
(identical(other.sleepTimerSettings, sleepTimerSettings) ||
|
||||
other.sleepTimerSettings == sleepTimerSettings));
|
||||
other.sleepTimerSettings == sleepTimerSettings) &&
|
||||
(identical(other.playbackReportInterval, playbackReportInterval) ||
|
||||
other.playbackReportInterval == playbackReportInterval));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
|
|
@ -477,7 +496,8 @@ class _$PlayerSettingsImpl implements _PlayerSettings {
|
|||
preferredDefaultVolume,
|
||||
preferredDefaultSpeed,
|
||||
const DeepCollectionEquality().hash(_speedOptions),
|
||||
sleepTimerSettings);
|
||||
sleepTimerSettings,
|
||||
playbackReportInterval);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
|
|
@ -501,7 +521,8 @@ abstract class _PlayerSettings implements PlayerSettings {
|
|||
final double preferredDefaultVolume,
|
||||
final double preferredDefaultSpeed,
|
||||
final List<double> speedOptions,
|
||||
final SleepTimerSettings sleepTimerSettings}) = _$PlayerSettingsImpl;
|
||||
final SleepTimerSettings sleepTimerSettings,
|
||||
final Duration playbackReportInterval}) = _$PlayerSettingsImpl;
|
||||
|
||||
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||
_$PlayerSettingsImpl.fromJson;
|
||||
|
|
@ -519,6 +540,8 @@ abstract class _PlayerSettings implements PlayerSettings {
|
|||
@override
|
||||
SleepTimerSettings get sleepTimerSettings;
|
||||
@override
|
||||
Duration get playbackReportInterval;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ _$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map<String, dynamic> json) =>
|
|||
? const SleepTimerSettings()
|
||||
: SleepTimerSettings.fromJson(
|
||||
json['sleepTimerSettings'] as Map<String, dynamic>),
|
||||
playbackReportInterval: json['playbackReportInterval'] == null
|
||||
? const Duration(seconds: 10)
|
||||
: Duration(
|
||||
microseconds: (json['playbackReportInterval'] as num).toInt()),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
||||
|
|
@ -57,6 +61,7 @@ Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
|||
'preferredDefaultSpeed': instance.preferredDefaultSpeed,
|
||||
'speedOptions': instance.speedOptions,
|
||||
'sleepTimerSettings': instance.sleepTimerSettings,
|
||||
'playbackReportInterval': instance.playbackReportInterval.inMicroseconds,
|
||||
};
|
||||
|
||||
_$ExpandedPlayerSettingsImpl _$$ExpandedPlayerSettingsImplFromJson(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Future<FutureOr<ColorScheme?>> themeFromCover(
|
|||
ImageProvider<Object> img, {
|
||||
Brightness brightness = Brightness.dark,
|
||||
}) async {
|
||||
// ! add deliberate delay to simulate a long running task
|
||||
// ! add deliberate delay to simulate a long running task as it interferes with other animations
|
||||
await Future.delayed(500.ms);
|
||||
|
||||
debugPrint('Generating color scheme from cover image');
|
||||
|
|
|
|||
56
pubspec.lock
56
pubspec.lock
|
|
@ -337,6 +337,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.6"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
duration_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -847,6 +863,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1015,6 +1047,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
sensors_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sensors_plus
|
||||
sha256: "6898cd4490ffc27fea4de5976585e92fae55355175d46c6c3b3d719d42f9e230"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
sensors_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sensors_plus_platform_interface
|
||||
sha256: bc472d6cfd622acb4f020e726433ee31788b038056691ba433fec80e448a094f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1339,6 +1387,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.1"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ dependencies:
|
|||
coast: ^2.0.2
|
||||
collection: ^1.18.0
|
||||
cupertino_icons: ^1.0.6
|
||||
device_info_plus: ^10.1.0
|
||||
duration_picker: ^1.2.0
|
||||
easy_stepper: ^0.8.4
|
||||
flutter:
|
||||
|
|
@ -66,12 +67,13 @@ dependencies:
|
|||
miniplayer:
|
||||
path: ../miniplayer
|
||||
numberpicker: ^2.1.2
|
||||
package_info_plus: ^8.0.0
|
||||
path: ^1.9.0
|
||||
path_provider: ^2.1.0
|
||||
riverpod_annotation: ^2.3.5
|
||||
rxdart: ^0.27.7
|
||||
scroll_loop_auto_scroll: ^0.0.5
|
||||
# sensors_plus: ^5.0.1
|
||||
sensors_plus: ^5.0.1
|
||||
shelfsdk:
|
||||
path: ../../_dart/shelfsdk
|
||||
shimmer: ^3.0.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue