import 'package:dio/dio.dart'; /// Thin client for POST /api/events — the play-event lifecycle the /// server uses for history, recommendation scoring, ListenBrainz /// scrobbles, and (since #415) system-playlist rotation. /// /// Mirrors the web events dispatcher's three calls. Best-effort by /// contract: callers swallow errors — a missed event is acceptable /// per the server spec's v1 stance, and the server's /// auto-close-prior-open keeps history sane even if an ended/skipped /// is lost. class EventsApi { EventsApi(this._dio); final Dio _dio; /// POST play_started. Returns the server's play_event_id (used to /// close the row later), or null if the call failed / response was /// malformed. `source` tags the originating system playlist /// ('for_you' | 'discover') so the server advances that rotation; /// omit for library / user-playlist / radio plays. Future playStarted({ required String trackId, required String clientId, String? source, }) async { final r = await _dio.post>( '/api/events', data: { 'type': 'play_started', 'track_id': trackId, 'client_id': clientId, if (source != null && source.isNotEmpty) 'source': source, }, ); return (r.data ?? const {})['play_event_id'] as String?; } Future playEnded({ required String playEventId, required int durationPlayedMs, }) async { await _dio.post( '/api/events', data: { 'type': 'play_ended', 'play_event_id': playEventId, 'duration_played_ms': durationPlayedMs, }, ); } Future playSkipped({ required String playEventId, required int positionMs, }) async { await _dio.post( '/api/events', data: { 'type': 'play_skipped', 'play_event_id': playEventId, 'position_ms': positionMs, }, ); } }