Files
sm-emby-share/lib/features/player/services/frame_jank_monitor.dart
T
2026-07-14 11:11:36 +08:00

105 lines
2.2 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import '../../../shared/utils/app_logger.dart';
const _tag = 'FrameJank';
@immutable
class FrameDiag {
final double fps;
final double lastBuildMs;
final double lastRasterMs;
final int jankCount;
const FrameDiag({
required this.fps,
required this.lastBuildMs,
required this.lastRasterMs,
required this.jankCount,
});
static const zero = FrameDiag(
fps: 0,
lastBuildMs: 0,
lastRasterMs: 0,
jankCount: 0,
);
}
class FrameJankMonitor {
FrameJankMonitor({this.jankThreshold = const Duration(milliseconds: 32)});
final Duration jankThreshold;
final ValueNotifier<FrameDiag> diag = ValueNotifier<FrameDiag>(
FrameDiag.zero,
);
TimingsCallback? _callback;
int _jankCount = 0;
int _windowFrames = 0;
final Stopwatch _windowClock = Stopwatch()..start();
double _fps = 0;
void start() {
if (kReleaseMode || _callback != null) return;
final cb = _onTimings;
_callback = cb;
SchedulerBinding.instance.addTimingsCallback(cb);
}
void _onTimings(List<FrameTiming> timings) {
if (timings.isEmpty) return;
for (final t in timings) {
if (t.buildDuration >= jankThreshold ||
t.rasterDuration >= jankThreshold) {
_jankCount++;
AppLogger.warn(
_tag,
'janky frame build=${_ms(t.buildDuration)} '
'raster=${_ms(t.rasterDuration)} total=${_ms(t.totalSpan)}',
);
}
}
_windowFrames += timings.length;
final elapsedMs = _windowClock.elapsedMilliseconds;
if (elapsedMs >= 500) {
_fps = _windowFrames * 1000 / elapsedMs;
_windowFrames = 0;
_windowClock.reset();
}
final last = timings.last;
diag.value = FrameDiag(
fps: _fps,
lastBuildMs: last.buildDuration.inMicroseconds / 1000,
lastRasterMs: last.rasterDuration.inMicroseconds / 1000,
jankCount: _jankCount,
);
}
void dispose() {
final cb = _callback;
if (cb != null) {
SchedulerBinding.instance.removeTimingsCallback(cb);
_callback = null;
}
diag.dispose();
}
static String _ms(Duration d) =>
'${(d.inMicroseconds / 1000).toStringAsFixed(1)}ms';
}