perf: optimize native worker snapshot writes with delta mode

Replace full-queue snapshot writes on every progress tick with a
lightweight delta mode that only includes the active item.

- Progress tick (1s loop) now writes item_delta with only the
  active item instead of serializing the entire queue
- Full compact_items snapshots are reserved for important events:
  start, pause/resume/cancel, item boundaries, finish, and
  service stop/destroy
- Compact items omit large static fields (item_json, track_name,
  artist_name) that Dart already has from queue restore
- Snapshot always carries item_ids for adoption correlation
- Dart-side _applyAndroidNativeWorkerSnapshot handles item_delta
  as a single-item fallback when items array is absent
- Dart-side _tryAdoptAndroidNativeWorkerSnapshot reads item_ids
  as fallback when items is not present
- Add deferred SAF publish: native worker writes to cache, runs
  all finalization locally, then publishes once to SAF at the end
- Forward defer_saf_publish through DownloadRequestPayload
This commit is contained in:
zarzet
2026-05-05 03:17:38 +07:00
parent 6b342aeac6
commit cfc8e699f3
6 changed files with 364 additions and 71 deletions
+29 -11
View File
@@ -4520,15 +4520,22 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
final rawItems = snapshot['items'];
if (rawItems is! List || rawItems.isEmpty) {
final rawItemIds = snapshot['item_ids'];
final snapshotIds = rawItems is List
? rawItems
.whereType<Map<Object?, Object?>>()
.map((item) => item['item_id']?.toString() ?? '')
.where((id) => id.isNotEmpty)
.toSet()
: rawItemIds is List
? rawItemIds
.map((id) => id?.toString() ?? '')
.where((id) => id.isNotEmpty)
.toSet()
: <String>{};
if (snapshotIds.isEmpty) {
return false;
}
final snapshotIds = rawItems
.whereType<Map<Object?, Object?>>()
.map((item) => item['item_id']?.toString() ?? '')
.where((id) => id.isNotEmpty)
.toSet();
if (!restoredItems.any((item) => snapshotIds.contains(item.id))) {
return false;
}
@@ -4958,6 +4965,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
safFileName: safFileName ?? '',
safOutputExt: safOutputExt,
stageSafOutput: isSafMode,
deferSafPublish: isSafMode,
requiresContainerConversion:
outputExt == '.flac' &&
_extensionRequiresNativeContainerConversion(item.service),
@@ -4984,13 +4992,23 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
AppSettings settings,
) async {
final rawItems = snapshot['items'];
if (rawItems is! List) {
final rawDelta = snapshot['item_delta'];
final itemSnapshots = <Map<String, dynamic>>[];
if (rawItems is List) {
for (final rawItem in rawItems) {
if (rawItem is Map) {
itemSnapshots.add(Map<String, dynamic>.from(rawItem));
}
}
}
if (rawDelta is Map) {
itemSnapshots.add(Map<String, dynamic>.from(rawDelta));
}
if (itemSnapshots.isEmpty) {
return;
}
for (final rawItem in rawItems) {
if (rawItem is! Map) continue;
final itemSnapshot = Map<String, dynamic>.from(rawItem);
for (final itemSnapshot in itemSnapshots) {
final itemId = itemSnapshot['item_id']?.toString() ?? '';
if (itemId.isEmpty || reconciledIds.contains(itemId)) {
continue;
@@ -44,6 +44,7 @@ class DownloadRequestPayload {
final String safFileName;
final String safOutputExt;
final bool stageSafOutput;
final bool deferSafPublish;
final bool requiresContainerConversion;
final String songLinkRegion;
@@ -91,6 +92,7 @@ class DownloadRequestPayload {
this.safFileName = '',
this.safOutputExt = '',
this.stageSafOutput = false,
this.deferSafPublish = false,
this.requiresContainerConversion = false,
this.songLinkRegion = 'US',
});
@@ -140,6 +142,7 @@ class DownloadRequestPayload {
'saf_file_name': safFileName,
'saf_output_ext': safOutputExt,
'stage_saf_output': stageSafOutput,
'defer_saf_publish': deferSafPublish,
'requires_container_conversion': requiresContainerConversion,
'songlink_region': songLinkRegion,
};
@@ -193,6 +196,7 @@ class DownloadRequestPayload {
safFileName: safFileName,
safOutputExt: safOutputExt,
stageSafOutput: stageSafOutput,
deferSafPublish: deferSafPublish,
requiresContainerConversion: requiresContainerConversion,
songLinkRegion: songLinkRegion,
);