feat(bugreport): auto-attach device + last-playback context
Some checks failed
Backend tests / test (push) Has been cancelled
Some checks failed
Backend tests / test (push) Has been cancelled
Playback reports ("doesn't start", "audio lags") came in with no way to know the
device or which source/server was used, forcing a follow-up question. Now the bug
report auto-context also carries:
- device = phone model / Android version (Platform.constants, zero-dep)
- play = last-played origin/host, mode, position, and last player error
New in-memory lastPlayback store written by PlayerScreen on source/status change, read
by BugReportFAB when composing a report. Reset on app restart (current session only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b62d22370b
commit
21500837ab
4 changed files with 82 additions and 0 deletions
|
|
@ -16,6 +16,13 @@ export type ChangelogEntry = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: ChangelogEntry[] = [
|
export const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
id: '2026-07-16',
|
||||||
|
date: 'July 2026',
|
||||||
|
items: [
|
||||||
|
'Bug reports now attach a bit of context automatically (your phone model, Android version, and the last source/server you played), so playback issues can be fixed without us having to ask you for details.',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: '2026-07-14c',
|
id: '2026-07-14c',
|
||||||
date: 'July 2026',
|
date: 'July 2026',
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import { captureScreen } from 'react-native-view-shot';
|
||||||
|
|
||||||
import { GoonClient } from '../api';
|
import { GoonClient } from '../api';
|
||||||
import { theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
|
import { formatLastPlayback } from '../lib/lastPlayback';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
client: GoonClient | null;
|
client: GoonClient | null;
|
||||||
|
|
@ -186,6 +187,19 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
||||||
} catch {
|
} catch {
|
||||||
// navRef nie ready — zostawiamy puste, backend i tak przyjmie nullable
|
// navRef nie ready — zostawiamy puste, backend i tak przyjmie nullable
|
||||||
}
|
}
|
||||||
|
// Model telefonu + wersja Androida — zawsze przydatne przy playback/UI bugach (żeby
|
||||||
|
// nie dopytywać usera "z czego korzystasz"). Platform.constants na Androidzie ma Model
|
||||||
|
// + Release; iOS fallback na Version.
|
||||||
|
const pc = Platform.constants as unknown as { Model?: string; Release?: string };
|
||||||
|
const device =
|
||||||
|
Platform.OS === 'android'
|
||||||
|
? `${pc?.Model || 'android'} / Android ${pc?.Release || Platform.Version}`
|
||||||
|
: `${Platform.OS} ${Platform.Version}`;
|
||||||
|
extraContext.push(`device=${device.slice(0, 48)}`);
|
||||||
|
// Ostatnie odtwarzanie (serwer/host/pozycja/błąd) — kluczowe dla "nie gra"/"audio
|
||||||
|
// się rozjeżdża", bo mówi KTÓRE źródło i gdzie, niezależnie od ekranu zgłoszenia.
|
||||||
|
const play = formatLastPlayback();
|
||||||
|
if (play) extraContext.push(`play=${play}`);
|
||||||
const sceneId =
|
const sceneId =
|
||||||
rawSceneId && /^[0-9a-f-]{36}$/.test(rawSceneId) ? rawSceneId : null;
|
rawSceneId && /^[0-9a-f-]{36}$/.test(rawSceneId) ? rawSceneId : null;
|
||||||
const finalMessage = extraContext.length > 0
|
const finalMessage = extraContext.length > 0
|
||||||
|
|
|
||||||
36
mobile/src/lib/lastPlayback.ts
Normal file
36
mobile/src/lib/lastPlayback.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* Ostatni kontekst odtwarzania (in-memory, bieżąca sesja apki). PlayerScreen go zapisuje,
|
||||||
|
* BugReportFAB dokleja do auto-kontekstu zgłoszenia. Dzięki temu playback-bugi ("nie gra",
|
||||||
|
* "audio się rozjeżdża") od razu niosą: które źródło/serwer, host CDN, pozycję i ostatni
|
||||||
|
* błąd, bez dopytywania usera. Reset przy restarcie apki (dotyczy tylko ostatniego odtwarzania).
|
||||||
|
*/
|
||||||
|
export type LastPlayback = {
|
||||||
|
origin?: string;
|
||||||
|
quality?: string;
|
||||||
|
host?: string;
|
||||||
|
positionSec?: number;
|
||||||
|
mode?: string;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _last: LastPlayback = {};
|
||||||
|
|
||||||
|
export function setLastPlayback(patch: LastPlayback): void {
|
||||||
|
_last = { ..._last, ...patch };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLastPlayback(): LastPlayback {
|
||||||
|
return _last;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Jednolinijkowy opis do doklejenia w bug-report (albo '' gdy nic nie grało). */
|
||||||
|
export function formatLastPlayback(): string {
|
||||||
|
const p = _last;
|
||||||
|
if (!p.origin && !p.host) return '';
|
||||||
|
const bits = [p.origin || p.host];
|
||||||
|
if (p.quality) bits.push(p.quality);
|
||||||
|
if (p.mode) bits.push(p.mode);
|
||||||
|
if (p.positionSec != null) bits.push(`@${p.positionSec}s`);
|
||||||
|
if (p.error) bits.push(`err:${p.error.slice(0, 60)}`);
|
||||||
|
return bits.filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import * as Haptics from 'expo-haptics';
|
||||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||||
import { useVideoPlayer, VideoView, type VideoSource } from 'expo-video';
|
import { useVideoPlayer, VideoView, type VideoSource } from 'expo-video';
|
||||||
import * as Clipboard from 'expo-clipboard';
|
import * as Clipboard from 'expo-clipboard';
|
||||||
|
import { setLastPlayback } from '../lib/lastPlayback';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
|
@ -210,6 +211,30 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
||||||
const playingEvent = useEvent(player, 'playingChange', { isPlaying: player.playing });
|
const playingEvent = useEvent(player, 'playingChange', { isPlaying: player.playing });
|
||||||
const isPlaying = playingEvent?.isPlaying ?? player.playing;
|
const isPlaying = playingEvent?.isPlaying ?? player.playing;
|
||||||
|
|
||||||
|
// Zapis kontekstu odtwarzania → BugReportFAB dokleja go do zgłoszeń (który serwer/host,
|
||||||
|
// pozycja, ostatni błąd) bez pytania usera. Aktualizowany na zmianę źródła i statusu.
|
||||||
|
React.useEffect(() => {
|
||||||
|
let host: string | undefined;
|
||||||
|
try {
|
||||||
|
host = new URL(url).host;
|
||||||
|
} catch {
|
||||||
|
// niepełny URL — pomijamy host
|
||||||
|
}
|
||||||
|
let pos: number | undefined;
|
||||||
|
try {
|
||||||
|
pos = Math.round(player.currentTime || 0);
|
||||||
|
} catch {
|
||||||
|
// player disposed — pomijamy
|
||||||
|
}
|
||||||
|
setLastPlayback({
|
||||||
|
origin: playOrigin,
|
||||||
|
host,
|
||||||
|
mode: (params.mode as string | undefined) || 'video',
|
||||||
|
positionSec: pos,
|
||||||
|
error: status === 'error' ? playerError?.message?.slice(0, 80) : undefined,
|
||||||
|
});
|
||||||
|
}, [url, playOrigin, status, playerError, player, params.mode]);
|
||||||
|
|
||||||
// Auto-fallback na WebView gdy native ExoPlayer dostanie błąd, a backend dostarczył
|
// Auto-fallback na WebView gdy native ExoPlayer dostanie błąd, a backend dostarczył
|
||||||
// embed URL. Najczęstsza przyczyna: IP-bound CDN URL (luluvids/iceyfile/tnmr) —
|
// embed URL. Najczęstsza przyczyna: IP-bound CDN URL (luluvids/iceyfile/tnmr) —
|
||||||
// backend extracted z VPS IP, mobile dostaje 403. WebView fetch'uje URL we własnym
|
// backend extracted z VPS IP, mobile dostaje 403. WebView fetch'uje URL we własnym
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue