/// Represents the seekable capture buffer for a live TV transcode session. /// /// Extracted from the `TranscodeSession` element in the tune response: /// - [startedAt] = `timeStamp` (epoch seconds, the buffer origin) /// - [seekStartSeconds] = `minOffsetAvailable` (earliest seekable offset) /// - [seekEndSeconds] = `maxOffsetAvailable` (latest seekable offset / live edge) class CaptureBuffer { final double startedAt; final double seekStartSeconds; final double seekEndSeconds; const CaptureBuffer({required this.startedAt, required this.seekStartSeconds, required this.seekEndSeconds}); /// Absolute epoch second of the earliest seekable point. int get seekableStartEpoch => (startedAt + seekStartSeconds).round(); /// Absolute epoch second of the latest seekable point (≈ live edge). int get seekableEndEpoch => (startedAt + seekEndSeconds).round(); /// Duration of the seekable range in seconds. int get seekableDurationSeconds => (seekEndSeconds - seekStartSeconds).round(); /// Parse from a TranscodeSession JSON map. Returns null if required fields are missing. /// Values may be num or String depending on whether the server returned JSON or XML. static CaptureBuffer? fromTranscodeSession(Map session) { final timeStamp = _parseDouble(session['timeStamp']); final minOffset = _parseDouble(session['minOffsetAvailable']); final maxOffset = _parseDouble(session['maxOffsetAvailable']); if (timeStamp == null || minOffset == null || maxOffset == null) return null; return CaptureBuffer(startedAt: timeStamp, seekStartSeconds: minOffset, seekEndSeconds: maxOffset); } static double? _parseDouble(dynamic value) { if (value is num) return value.toDouble(); if (value is String) return double.tryParse(value); return null; } @override String toString() => 'CaptureBuffer(startedAt: $startedAt, seek: $seekStartSeconds..$seekEndSeconds, ' 'range: $seekableStartEpoch..$seekableEndEpoch)'; }