hondavert-dev/lib/core/datalog/datalog_player.dart
HVBT Dev 332dd6209e v1.3.0 — Phase 3: SQLite datalog (DB + recorder + player + providers)
- 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
2026-04-13 20:42:26 +05:30

120 lines
3.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:typed_data';
import 'datalog_db.dart';
enum PlaybackStatus { idle, playing, paused }
/// Loads stored frames from SQLite and replays them through a stream
/// at configurable speed. Consumers watch [frameStream] exactly like
/// the live BT stream — the parser layer is unaware of the difference.
class DatalogPlayer {
final DatalogDb _db;
List<StoredFrame> _frames = [];
int _position = 0; // index into _frames
double _speed = 1.0; // 1.0 = real-time, 2.0 = 2× faster
PlaybackStatus _status = PlaybackStatus.idle;
PlaybackStatus get status => _status;
int get position => _position;
int get totalFrames => _frames.length;
double get speed => _speed;
set speed(double v) => _speed = v.clamp(0.25, 8.0);
final StreamController<Uint8List> _controller =
StreamController<Uint8List>.broadcast();
Stream<Uint8List> get frameStream => _controller.stream;
/// Callback fired on each tick — lets UI update a progress indicator.
void Function(int position, int total)? onProgress;
Timer? _timer;
DatalogPlayer(this._db);
/// Load all frames for [sessionId] into memory.
Future<void> load(int sessionId) async {
await stop();
_frames = await _db.getFrames(sessionId);
_position = 0;
_status = PlaybackStatus.idle;
}
/// Start or resume playback.
void play() {
if (_status == PlaybackStatus.playing) return;
if (_frames.isEmpty) return;
_status = PlaybackStatus.playing;
_scheduleNext();
}
void pause() {
_timer?.cancel();
_timer = null;
if (_status == PlaybackStatus.playing) {
_status = PlaybackStatus.paused;
}
}
Future<void> stop() async {
_timer?.cancel();
_timer = null;
_position = 0;
_status = PlaybackStatus.idle;
}
/// Jump to a specific frame index.
void seek(int index) {
_position = index.clamp(0, _frames.length - 1);
onProgress?.call(_position, _frames.length);
}
void _scheduleNext() {
if (_position >= _frames.length) {
_status = PlaybackStatus.idle;
onProgress?.call(_frames.length, _frames.length);
return;
}
// Calculate delay to next frame using original timestamps
Duration delay = const Duration(milliseconds: 100); // default 10 Hz
if (_position + 1 < _frames.length) {
final gap = _frames[_position + 1].timestamp
.difference(_frames[_position].timestamp);
delay = Duration(
microseconds: (gap.inMicroseconds / _speed).round(),
);
// Clamp to 10ms2s to handle gaps from pauses/reconnects
delay = delay.clamp(
const Duration(milliseconds: 10),
const Duration(seconds: 2),
);
}
_timer = Timer(delay, () {
if (_status != PlaybackStatus.playing) return;
if (_position < _frames.length) {
_controller.add(_frames[_position].data);
onProgress?.call(_position, _frames.length);
_position++;
}
_scheduleNext();
});
}
void dispose() {
_timer?.cancel();
_controller.close();
}
}
extension on Duration {
Duration clamp(Duration min, Duration max) {
if (this < min) return min;
if (this > max) return max;
return this;
}
}