- datalog_session.dart: DatalogSession model with toMap/fromMap, duration helpers - datalog_db.dart: SQLite schema (sessions + frames tables), batch insertFrames, getSessions, getFrames, deleteSession, closeSession - datalog_recorder.dart: listens to raw frame stream, buffers PendingFrames, flushes to DB every 500ms via Timer, finalises session on stop() - datalog_player.dart: loads StoredFrames from DB, replays at original timing adjusted by speed multiplier (0.25x–8x), play/pause/stop/seek, onProgress cb - datalog_provider.dart: datalogDbProvider, datalogRecorderProvider, datalogPlayerProvider singletons; RecordingNotifier (idle/recording state); sessionListProvider FutureProvider; PlaybackNotifier with full playback state - pubspec.yaml: added path ^1.9.0 dependency
95 lines
2.2 KiB
Dart
95 lines
2.2 KiB
Dart
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
import 'datalog_db.dart';
|
|
import '../models/datalog_session.dart';
|
|
|
|
/// Listens to a raw frame stream and batches writes to SQLite every 500ms.
|
|
class DatalogRecorder {
|
|
final DatalogDb _db;
|
|
|
|
int? _sessionId;
|
|
String? _ecuType;
|
|
DateTime? _startTime;
|
|
int _frameCount = 0;
|
|
|
|
final List<PendingFrame> _batch = [];
|
|
Timer? _flushTimer;
|
|
StreamSubscription<Uint8List>? _frameSub;
|
|
|
|
bool get isRecording => _sessionId != null;
|
|
int get frameCount => _frameCount;
|
|
int? get sessionId => _sessionId;
|
|
|
|
DatalogRecorder(this._db);
|
|
|
|
/// Start a new recording session, listening to [frameStream].
|
|
Future<void> start(Stream<Uint8List> frameStream, String ecuType) async {
|
|
if (isRecording) await stop();
|
|
|
|
_ecuType = ecuType;
|
|
_startTime = DateTime.now();
|
|
_frameCount = 0;
|
|
_batch.clear();
|
|
|
|
final session = DatalogSession(
|
|
ecuType: ecuType,
|
|
startTime: _startTime!,
|
|
);
|
|
_sessionId = await _db.insertSession(session);
|
|
|
|
// Collect frames into batch buffer
|
|
_frameSub = frameStream.listen((Uint8List frame) {
|
|
_batch.add(PendingFrame(DateTime.now(), frame));
|
|
_frameCount++;
|
|
});
|
|
|
|
// Flush to DB every 500ms
|
|
_flushTimer = Timer.periodic(const Duration(milliseconds: 500), (_) {
|
|
_flush();
|
|
});
|
|
}
|
|
|
|
/// Stop recording and finalise the session row.
|
|
Future<DatalogSession?> stop() async {
|
|
if (!isRecording) return null;
|
|
|
|
_flushTimer?.cancel();
|
|
_flushTimer = null;
|
|
await _frameSub?.cancel();
|
|
_frameSub = null;
|
|
|
|
await _flush(); // write any remaining buffered frames
|
|
|
|
final endTime = DateTime.now();
|
|
await _db.closeSession(_sessionId!, endTime, _frameCount);
|
|
|
|
final finished = DatalogSession(
|
|
id: _sessionId,
|
|
ecuType: _ecuType!,
|
|
startTime: _startTime!,
|
|
endTime: endTime,
|
|
frameCount: _frameCount,
|
|
);
|
|
|
|
_sessionId = null;
|
|
_ecuType = null;
|
|
_startTime = null;
|
|
_frameCount = 0;
|
|
|
|
return finished;
|
|
}
|
|
|
|
Future<void> _flush() async {
|
|
if (_batch.isEmpty || _sessionId == null) return;
|
|
final toWrite = List<PendingFrame>.from(_batch);
|
|
_batch.clear();
|
|
await _db.insertFrames(_sessionId!, toWrite);
|
|
}
|
|
|
|
void dispose() {
|
|
_flushTimer?.cancel();
|
|
_frameSub?.cancel();
|
|
}
|
|
}
|