- 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
207 lines
5.8 KiB
Dart
207 lines
5.8 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../datalog/datalog_db.dart';
|
|
import '../datalog/datalog_player.dart';
|
|
import '../datalog/datalog_recorder.dart';
|
|
import '../models/datalog_session.dart';
|
|
import 'bt_provider.dart';
|
|
import 'settings_provider.dart';
|
|
|
|
// ─── Singletons ───────────────────────────────────────────────────────────────
|
|
|
|
final datalogDbProvider = Provider<DatalogDb>((ref) {
|
|
final db = DatalogDb();
|
|
ref.onDispose(db.close);
|
|
return db;
|
|
});
|
|
|
|
final datalogRecorderProvider = Provider<DatalogRecorder>((ref) {
|
|
final recorder = DatalogRecorder(ref.watch(datalogDbProvider));
|
|
ref.onDispose(recorder.dispose);
|
|
return recorder;
|
|
});
|
|
|
|
final datalogPlayerProvider = Provider<DatalogPlayer>((ref) {
|
|
final player = DatalogPlayer(ref.watch(datalogDbProvider));
|
|
ref.onDispose(player.dispose);
|
|
return player;
|
|
});
|
|
|
|
// ─── Recording state ──────────────────────────────────────────────────────────
|
|
|
|
enum RecordingStatus { idle, recording }
|
|
|
|
class RecordingState {
|
|
final RecordingStatus status;
|
|
final int frameCount;
|
|
final int? sessionId;
|
|
|
|
const RecordingState({
|
|
this.status = RecordingStatus.idle,
|
|
this.frameCount = 0,
|
|
this.sessionId,
|
|
});
|
|
|
|
bool get isRecording => status == RecordingStatus.recording;
|
|
|
|
RecordingState copyWith({
|
|
RecordingStatus? status,
|
|
int? frameCount,
|
|
int? sessionId,
|
|
}) =>
|
|
RecordingState(
|
|
status: status ?? this.status,
|
|
frameCount: frameCount ?? this.frameCount,
|
|
sessionId: sessionId ?? this.sessionId,
|
|
);
|
|
}
|
|
|
|
class RecordingNotifier extends StateNotifier<RecordingState> {
|
|
final Ref _ref;
|
|
|
|
RecordingNotifier(this._ref) : super(const RecordingState());
|
|
|
|
Future<void> startRecording() async {
|
|
if (state.isRecording) return;
|
|
|
|
final recorder = _ref.read(datalogRecorderProvider);
|
|
final btNotifier = _ref.read(btProvider.notifier);
|
|
final ecuType = _ref.read(settingsProvider).ecuType.name;
|
|
|
|
await recorder.start(btNotifier.frameStream, ecuType);
|
|
|
|
state = RecordingState(
|
|
status: RecordingStatus.recording,
|
|
sessionId: recorder.sessionId,
|
|
);
|
|
}
|
|
|
|
Future<DatalogSession?> stopRecording() async {
|
|
if (!state.isRecording) return null;
|
|
|
|
final recorder = _ref.read(datalogRecorderProvider);
|
|
final session = await recorder.stop();
|
|
|
|
state = const RecordingState();
|
|
// Refresh session list
|
|
_ref.invalidate(sessionListProvider);
|
|
return session;
|
|
}
|
|
|
|
/// Called periodically from UI to update live frame count display.
|
|
void syncFrameCount() {
|
|
if (!state.isRecording) return;
|
|
final count = _ref.read(datalogRecorderProvider).frameCount;
|
|
if (count != state.frameCount) {
|
|
state = state.copyWith(frameCount: count);
|
|
}
|
|
}
|
|
}
|
|
|
|
final recordingProvider =
|
|
StateNotifierProvider<RecordingNotifier, RecordingState>(
|
|
(ref) => RecordingNotifier(ref),
|
|
);
|
|
|
|
// ─── Session list ─────────────────────────────────────────────────────────────
|
|
|
|
final sessionListProvider = FutureProvider<List<DatalogSession>>((ref) {
|
|
return ref.watch(datalogDbProvider).getSessions();
|
|
});
|
|
|
|
// ─── Playback state ───────────────────────────────────────────────────────────
|
|
|
|
class PlaybackState {
|
|
final bool isActive;
|
|
final int? sessionId;
|
|
final int position;
|
|
final int total;
|
|
final double speed;
|
|
final PlaybackStatus status;
|
|
|
|
const PlaybackState({
|
|
this.isActive = false,
|
|
this.sessionId,
|
|
this.position = 0,
|
|
this.total = 0,
|
|
this.speed = 1.0,
|
|
this.status = PlaybackStatus.idle,
|
|
});
|
|
|
|
double get progress => total == 0 ? 0 : position / total;
|
|
|
|
PlaybackState copyWith({
|
|
bool? isActive,
|
|
int? sessionId,
|
|
int? position,
|
|
int? total,
|
|
double? speed,
|
|
PlaybackStatus? status,
|
|
}) =>
|
|
PlaybackState(
|
|
isActive: isActive ?? this.isActive,
|
|
sessionId: sessionId ?? this.sessionId,
|
|
position: position ?? this.position,
|
|
total: total ?? this.total,
|
|
speed: speed ?? this.speed,
|
|
status: status ?? this.status,
|
|
);
|
|
}
|
|
|
|
class PlaybackNotifier extends StateNotifier<PlaybackState> {
|
|
final Ref _ref;
|
|
|
|
PlaybackNotifier(this._ref) : super(const PlaybackState());
|
|
|
|
Future<void> loadSession(int sessionId) async {
|
|
final player = _ref.read(datalogPlayerProvider);
|
|
await player.load(sessionId);
|
|
|
|
player.onProgress = (pos, total) {
|
|
state = state.copyWith(
|
|
position: pos,
|
|
total: total,
|
|
status: player.status,
|
|
);
|
|
};
|
|
|
|
state = PlaybackState(
|
|
isActive: true,
|
|
sessionId: sessionId,
|
|
total: player.totalFrames,
|
|
speed: player.speed,
|
|
status: PlaybackStatus.idle,
|
|
);
|
|
}
|
|
|
|
void play() {
|
|
_ref.read(datalogPlayerProvider).play();
|
|
state = state.copyWith(status: PlaybackStatus.playing);
|
|
}
|
|
|
|
void pause() {
|
|
_ref.read(datalogPlayerProvider).pause();
|
|
state = state.copyWith(status: PlaybackStatus.paused);
|
|
}
|
|
|
|
Future<void> stop() async {
|
|
await _ref.read(datalogPlayerProvider).stop();
|
|
state = const PlaybackState();
|
|
}
|
|
|
|
void seek(int index) {
|
|
_ref.read(datalogPlayerProvider).seek(index);
|
|
state = state.copyWith(position: index);
|
|
}
|
|
|
|
void setSpeed(double speed) {
|
|
_ref.read(datalogPlayerProvider).speed = speed;
|
|
state = state.copyWith(speed: speed);
|
|
}
|
|
}
|
|
|
|
final playbackProvider =
|
|
StateNotifierProvider<PlaybackNotifier, PlaybackState>(
|
|
(ref) => PlaybackNotifier(ref),
|
|
);
|