// Author: fruit.com.cn import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; class PlayerFrameCapture { const PlayerFrameCapture._(); static const int budgetMilliseconds = 32; static Future capture({ required BuildContext context, required GlobalKey boundaryKey, required String source, }) async { final boundary = boundaryKey.currentContext?.findRenderObject(); if (boundary is! RenderRepaintBoundary) { _log('[player-entry] phase=frame-capture-unavailable source=$source'); return null; } final stopwatch = Stopwatch()..start(); try { final pixelRatio = MediaQuery.devicePixelRatioOf( context, ).clamp(1.0, 2.0).toDouble(); final image = await boundary.toImage(pixelRatio: pixelRatio); final data = await image.toByteData(format: ui.ImageByteFormat.png); image.dispose(); if (stopwatch.elapsedMilliseconds > budgetMilliseconds) { _log( '[player-entry] phase=frame-capture-over-budget source=$source ' 'ms=${stopwatch.elapsedMilliseconds} budget_ms=$budgetMilliseconds', ); } if (data == null) { _log('[player-entry] phase=frame-capture-empty source=$source'); return null; } final bytes = data.buffer.asUint8List( data.offsetInBytes, data.lengthInBytes, ); _log( '[player-entry] phase=frame-captured source=$source ' 'bytes=${bytes.length} ms=${stopwatch.elapsedMilliseconds}', ); return bytes; } catch (error) { _log( '[player-entry] phase=frame-capture-failed source=$source ' 'error=$error', ); return null; } } static void _log(String message) { if (kDebugMode) debugPrint(message); } }