99e1df4920
android / Build + lint + test (push) Failing after 2m7s
Operator hit a track that loaded with zero duration; player just sat on it. Two things needed: skip the dead track immediately, and tell the server so the admin inbox can surface the bad file. PlayerController: - Player.Listener.onPlaybackStateChanged(STATE_READY) now checks duration. If it's <= 0 or C.TIME_UNSET, fires a PlaybackErrorEvent with kind="zero_duration" and calls seekToNextMediaItem (or stop if it was the last item). Per-item evaluation guard keeps repeat STATE_READY events (post-seek, post-resume) from re-firing. - onPlayerError now also surfaces a PlaybackErrorEvent with kind="load_failed" + the Media3 exception message as detail. - playbackErrorEvents flow changes from Flow<String> (title only) to Flow<PlaybackErrorEvent> (track_id + kind + title + detail) so downstream consumers can both surface a snackbar AND POST to the admin inbox without duplicating event emission. PlaybackErrorRepository (new): - Wraps POST /api/playback-errors with the offline-first MutationQueue fallback per the standing rule for server writes. - Reuses AuthStore.clientId for the client_id field — same UUID-per- install identifier the play-events reporter sends, so support can correlate playback errors with surrounding plays. PlaybackErrorReporter: - Consumes the new richer event shape. Fires the server report per event (no debounce — the admin inbox should capture every report, not a coalesced summary). Continues to debounce the user-facing snackbar in the 2s window so a burst doesn't spam toasts. MutationQueue / MutationReplayer: - Adds PLAYBACK_ERROR_REPORT kind + PlaybackErrorReportPayload + enqueuePlaybackErrorReport entry point + replayer dispatch case hitting the new PlaybackErrorsApi. Web admin inbox + UI is the next commit.