feat: downloads

This commit is contained in:
edde746
2025-12-12 02:59:27 +01:00
parent b7c630f10e
commit dc44354036
65 changed files with 28245 additions and 6883 deletions
+77 -38
View File
@@ -3,20 +3,33 @@ import 'dart:async';
import '../mpv/mpv.dart';
import 'plex_client.dart';
import 'offline_watch_sync_service.dart';
import '../models/plex_metadata.dart';
import '../utils/app_logger.dart';
/// Tracks playback progress and reports it to the Plex server.
///
/// Handles:
/// - Periodic timeline updates during playback
/// - Periodic timeline updates during playback (online) or queuing (offline)
/// - Resume position tracking
/// - State change reporting (playing, paused, stopped)
/// - Offline progress queuing for later sync
class PlaybackProgressTracker {
final PlexClient client;
/// Plex client for online progress updates (null when offline)
final PlexClient? client;
/// Metadata of the media being played
final PlexMetadata metadata;
/// Video player instance
final Player player;
/// Whether playback is in offline mode
final bool isOffline;
/// Service for queuing offline progress updates
final OfflineWatchSyncService? offlineWatchService;
/// Timer for periodic progress updates
Timer? _progressTimer;
@@ -27,12 +40,22 @@ class PlaybackProgressTracker {
required this.client,
required this.metadata,
required this.player,
this.isOffline = false,
this.offlineWatchService,
this.updateInterval = const Duration(seconds: 10),
});
}) : assert(
!isOffline || offlineWatchService != null,
'offlineWatchService is required when isOffline is true',
),
assert(
isOffline || client != null,
'client is required when isOffline is false',
);
/// Start tracking playback progress
///
/// Begins periodic timeline updates to the Plex server.
/// Begins periodic timeline updates to the Plex server (online)
/// or queuing progress updates locally (offline).
void startTracking() {
if (_progressTimer != null) {
appLogger.w('Progress tracking already started');
@@ -46,7 +69,7 @@ class PlaybackProgressTracker {
});
appLogger.d(
'Started progress tracking (interval: ${updateInterval.inSeconds}s)',
'Started progress tracking (interval: ${updateInterval.inSeconds}s, offline: $isOffline)',
);
}
@@ -59,7 +82,7 @@ class PlaybackProgressTracker {
appLogger.d('Stopped progress tracking');
}
/// Send progress update to Plex server
/// Send progress update to Plex server or queue locally
///
/// [state] can be 'playing', 'paused', or 'stopped'
Future<void> sendProgress(String state) async {
@@ -76,44 +99,60 @@ class PlaybackProgressTracker {
return;
}
// Build timeline query parameters
final queryParams = {
'ratingKey': metadata.ratingKey,
'key': '/library/metadata/${metadata.ratingKey}',
'state': state,
'time': position.inMilliseconds.toString(),
'duration': duration.inMilliseconds.toString(),
};
// Add playQueueItemID if available (for playlist/shuffle playback)
if (metadata.playQueueItemID != null) {
queryParams['playQueueItemID'] = metadata.playQueueItemID.toString();
if (isOffline) {
// Queue progress update for later sync
await _sendOfflineProgress(position, duration);
} else {
// Send progress to server immediately
await _sendOnlineProgress(state, position, duration);
}
// Send timeline update
await client
.updateProgress(
metadata.ratingKey,
time: position.inMilliseconds,
state: state,
duration: duration.inMilliseconds,
)
.catchError((error) {
// Silent handling - don't interrupt playback for progress errors
appLogger.d(
'Failed to update progress (non-critical)',
error: error,
);
});
appLogger.d(
'Progress update sent: $state at ${position.inSeconds}s / ${duration.inSeconds}s',
);
} catch (e) {
appLogger.d('Failed to send progress update (non-critical)', error: e);
}
}
/// Send progress update to Plex server (online mode)
Future<void> _sendOnlineProgress(
String state,
Duration position,
Duration duration,
) async {
await client!.updateProgress(
metadata.ratingKey,
time: position.inMilliseconds,
state: state,
duration: duration.inMilliseconds,
);
appLogger.d(
'Progress update sent: $state at ${position.inSeconds}s / ${duration.inSeconds}s',
);
}
/// Queue progress update locally (offline mode)
Future<void> _sendOfflineProgress(
Duration position,
Duration duration,
) async {
final serverId = metadata.serverId;
if (serverId == null) {
appLogger.w('Cannot queue offline progress: serverId is null');
return;
}
await offlineWatchService!.queueProgressUpdate(
serverId: serverId,
ratingKey: metadata.ratingKey,
viewOffset: position.inMilliseconds,
duration: duration.inMilliseconds,
);
final percent = (position.inMilliseconds / duration.inMilliseconds * 100);
appLogger.d(
'Offline progress queued: ${position.inSeconds}s / ${duration.inSeconds}s (${percent.toStringAsFixed(1)}%)',
);
}
/// Dispose resources
void dispose() {
stopTracking();