原生解析问题
This commit is contained in:
parent
2e9bb9bddc
commit
8ac9590a8f
@ -132,6 +132,8 @@ class GiftMp4VideoPlatformView(
|
||||
}
|
||||
}
|
||||
|
||||
private const val GIFT_MP4_VAPC_SCAN_BYTES = 512 * 1024
|
||||
|
||||
private class GiftMp4AlphaTextureView(context: Context) :
|
||||
TextureView(context),
|
||||
TextureView.SurfaceTextureListener {
|
||||
@ -826,7 +828,7 @@ private data class GiftMp4NativeLayout(
|
||||
val marker = "vapc".toByteArray(Charsets.US_ASCII)
|
||||
val markerIndex = bytes.indexOf(marker, 0)
|
||||
if (markerIndex < 0) return null
|
||||
val searchEnd = min(bytes.size, markerIndex + marker.size + 512 * 1024)
|
||||
val searchEnd = min(bytes.size, markerIndex + marker.size + GIFT_MP4_VAPC_SCAN_BYTES)
|
||||
for (index in markerIndex + marker.size until searchEnd) {
|
||||
if (bytes[index] != '{'.code.toByte()) continue
|
||||
val end = findJsonObjectEnd(bytes, index, searchEnd)
|
||||
@ -991,7 +993,21 @@ private data class GiftMp4ResolvedSource(
|
||||
|
||||
fun readBytes(): ByteArray? =
|
||||
runCatching {
|
||||
file.readBytes()
|
||||
file.inputStream().use { input ->
|
||||
val readLength = min(file.length(), GIFT_MP4_VAPC_SCAN_BYTES.toLong()).toInt()
|
||||
if (readLength <= 0) {
|
||||
return@use ByteArray(0)
|
||||
}
|
||||
val buffer = ByteArray(readLength)
|
||||
val bytesRead = input.read(buffer)
|
||||
if (bytesRead <= 0) {
|
||||
ByteArray(0)
|
||||
} else if (bytesRead == buffer.size) {
|
||||
buffer
|
||||
} else {
|
||||
buffer.copyOf(bytesRead)
|
||||
}
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
fun close() = Unit
|
||||
|
||||
@ -727,43 +727,38 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
targetGroupKey == null ||
|
||||
_markGiftVisualBatchForPlayback(msg, targetGroupKey);
|
||||
if (shouldPlayBatchVisuals) {
|
||||
SCRoomEffectScheduler().scheduleDeferredEffect(
|
||||
debugLabel: 'room_gift_ticker_${msg.type}_${msg.gift?.id ?? ""}',
|
||||
action: () {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
var giftModel = LGiftModel();
|
||||
giftModel.labelId = _buildGiftTickerLabelId(msg, targetGroupKey);
|
||||
giftModel.sendUserName = msg.user?.userNickname ?? "";
|
||||
giftModel.sendToUserName = _resolveGiftTickerTargetName(
|
||||
msg,
|
||||
targetUserIds,
|
||||
);
|
||||
giftModel.sendUserPic = msg.user?.userAvatar ?? "";
|
||||
giftModel.giftPic = msg.gift?.giftPhoto ?? "";
|
||||
giftModel.giftCount = msg.number ?? 0;
|
||||
giftModel.giftCountStepUnit = _resolveGiftCountStepUnit(msg);
|
||||
unawaited(
|
||||
warmImageResource(
|
||||
giftModel.sendUserPic,
|
||||
logicalWidth: 26.w,
|
||||
logicalHeight: 26.w,
|
||||
),
|
||||
);
|
||||
unawaited(
|
||||
warmImageResource(
|
||||
giftModel.giftPic,
|
||||
logicalWidth: 34.w,
|
||||
logicalHeight: 34.w,
|
||||
),
|
||||
);
|
||||
Provider.of<GiftAnimationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).enqueueGiftAnimation(giftModel);
|
||||
},
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
var giftModel = LGiftModel();
|
||||
giftModel.labelId = _buildGiftTickerLabelId(msg, targetGroupKey);
|
||||
giftModel.sendUserName = msg.user?.userNickname ?? "";
|
||||
giftModel.sendToUserName = _resolveGiftTickerTargetName(
|
||||
msg,
|
||||
targetUserIds,
|
||||
);
|
||||
giftModel.sendUserPic = msg.user?.userAvatar ?? "";
|
||||
giftModel.giftPic = msg.gift?.giftPhoto ?? "";
|
||||
giftModel.giftCount = msg.number ?? 0;
|
||||
giftModel.giftCountStepUnit = _resolveGiftCountStepUnit(msg);
|
||||
unawaited(
|
||||
warmImageResource(
|
||||
giftModel.sendUserPic,
|
||||
logicalWidth: 26.w,
|
||||
logicalHeight: 26.w,
|
||||
),
|
||||
);
|
||||
unawaited(
|
||||
warmImageResource(
|
||||
giftModel.giftPic,
|
||||
logicalWidth: 34.w,
|
||||
logicalHeight: 34.w,
|
||||
),
|
||||
);
|
||||
Provider.of<GiftAnimationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).enqueueGiftAnimation(giftModel);
|
||||
} else {
|
||||
debugPrint(
|
||||
'[GiftFx][voice] skip gift ticker by visual batch dedupe '
|
||||
|
||||
@ -338,11 +338,9 @@ class _RecentMicChangeSeatSnapshot {
|
||||
class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
static const String _roomRocketPagLaunchDialogTag =
|
||||
'showRoomRocketPagLaunchDialog';
|
||||
static const List<Duration> _roomRocketGiftRefreshDelays = [
|
||||
Duration(milliseconds: 700),
|
||||
Duration(milliseconds: 1600),
|
||||
Duration(milliseconds: 3200),
|
||||
];
|
||||
static const Duration _roomRocketGiftRefreshThrottleDelay = Duration(
|
||||
milliseconds: 700,
|
||||
);
|
||||
static const List<Duration> _roomRocketPostLaunchRefreshDelays = [
|
||||
Duration(milliseconds: 800),
|
||||
Duration(seconds: 2),
|
||||
@ -407,7 +405,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
Timer? _roomEntryEffectTimer;
|
||||
Timer? _roomRocketLaunchAnimationTimer;
|
||||
ValueNotifier<String?>? _roomRocketLaunchTopAvatarNotifier;
|
||||
final List<Timer> _roomRocketGiftRefreshTimers = [];
|
||||
Timer? _roomRocketGiftRefreshTimer;
|
||||
String? _roomRocketGiftRefreshRoomId;
|
||||
bool _roomRocketGiftRefreshDirty = false;
|
||||
bool _roomRocketGiftRefreshInFlight = false;
|
||||
final List<Timer> _roomRocketPostLaunchRefreshTimers = [];
|
||||
final List<Timer> _roomRocketEntryReadyFallbackTimers = [];
|
||||
Timer? _roomRedPacketPresenceTimer;
|
||||
@ -4122,25 +4123,63 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
if (resolvedRoomId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_cancelRoomRocketGiftStatusRefresh();
|
||||
for (final delay in _roomRocketGiftRefreshDelays) {
|
||||
final timer = Timer(delay, () {
|
||||
_roomRocketGiftRefreshTimers.removeWhere((item) => !item.isActive);
|
||||
if (resolvedRoomId !=
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
|
||||
return;
|
||||
}
|
||||
unawaited(refreshRoomRocketStatus(roomId: resolvedRoomId));
|
||||
});
|
||||
_roomRocketGiftRefreshTimers.add(timer);
|
||||
_roomRocketGiftRefreshRoomId = resolvedRoomId;
|
||||
_roomRocketGiftRefreshDirty = true;
|
||||
if (_roomRocketGiftRefreshTimer != null || _roomRocketGiftRefreshInFlight) {
|
||||
return;
|
||||
}
|
||||
_roomRocketGiftRefreshTimer = Timer(
|
||||
_roomRocketGiftRefreshThrottleDelay,
|
||||
() => unawaited(_runRoomRocketGiftStatusRefresh()),
|
||||
);
|
||||
}
|
||||
|
||||
void _schedulePendingRoomRocketGiftStatusRefreshIfNeeded() {
|
||||
if (!_roomRocketGiftRefreshDirty || _roomRocketGiftRefreshTimer != null) {
|
||||
return;
|
||||
}
|
||||
final pendingRoomId = (_roomRocketGiftRefreshRoomId ?? "").trim();
|
||||
final currentRoomId =
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||
if (pendingRoomId.isEmpty || pendingRoomId != currentRoomId) {
|
||||
return;
|
||||
}
|
||||
_roomRocketGiftRefreshTimer = Timer(
|
||||
_roomRocketGiftRefreshThrottleDelay,
|
||||
() => unawaited(_runRoomRocketGiftStatusRefresh()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runRoomRocketGiftStatusRefresh() async {
|
||||
_roomRocketGiftRefreshTimer = null;
|
||||
if (_roomRocketGiftRefreshInFlight) {
|
||||
return;
|
||||
}
|
||||
final resolvedRoomId = (_roomRocketGiftRefreshRoomId ?? "").trim();
|
||||
if (resolvedRoomId.isEmpty) {
|
||||
_roomRocketGiftRefreshDirty = false;
|
||||
return;
|
||||
}
|
||||
_roomRocketGiftRefreshDirty = false;
|
||||
if (resolvedRoomId !=
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
|
||||
_schedulePendingRoomRocketGiftStatusRefreshIfNeeded();
|
||||
return;
|
||||
}
|
||||
_roomRocketGiftRefreshInFlight = true;
|
||||
try {
|
||||
await refreshRoomRocketStatus(roomId: resolvedRoomId);
|
||||
} finally {
|
||||
_roomRocketGiftRefreshInFlight = false;
|
||||
_schedulePendingRoomRocketGiftStatusRefreshIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
void _cancelRoomRocketGiftStatusRefresh() {
|
||||
for (final timer in _roomRocketGiftRefreshTimers) {
|
||||
timer.cancel();
|
||||
}
|
||||
_roomRocketGiftRefreshTimers.clear();
|
||||
_roomRocketGiftRefreshTimer?.cancel();
|
||||
_roomRocketGiftRefreshTimer = null;
|
||||
_roomRocketGiftRefreshRoomId = null;
|
||||
_roomRocketGiftRefreshDirty = false;
|
||||
}
|
||||
|
||||
void _scheduleRoomRocketEntryReadyFallback(String roomId) {
|
||||
@ -4384,8 +4423,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
}
|
||||
SCGiftVapSvgaManager().play(
|
||||
animationUrl,
|
||||
priority: SCGiftVapSvgaManager.entryEffectPriority + 100,
|
||||
type: SCGiftVapSvgaManager.entryEffectType,
|
||||
priority: SCGiftVapSvgaManager.rocketLaunchEffectPriority,
|
||||
type: SCGiftVapSvgaManager.rocketLaunchEffectType,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -5149,8 +5149,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
RoomRocketLaunchBroadcastMessage launch,
|
||||
) {
|
||||
_clearRoomRocketLaunchCountdownForRoom(launch.roomId);
|
||||
_pendingRoomRocketRewardPopups.remove(launch.roomId);
|
||||
_recentRoomRocketRewardProbeTimes.remove(launch.roomId);
|
||||
_markRoomRocketLaunchEffectConsumed(launch);
|
||||
}
|
||||
|
||||
@ -6111,10 +6109,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
currentContext,
|
||||
listen: false,
|
||||
);
|
||||
rtcProvider.advanceRoomRocketStatusAfterLaunch(launch);
|
||||
rtcProvider.scheduleRoomRocketPostLaunchStatusRefresh(
|
||||
roomId: launchRoomId,
|
||||
);
|
||||
if (_isCurrentVisibleVoiceRoom(launchRoomId)) {
|
||||
_markRoomRocketRewardProbe(launch.roomId);
|
||||
_playRoomRocketLaunchEffectsAndReward(launch);
|
||||
} else {
|
||||
rtcProvider.advanceRoomRocketStatusAfterLaunch(launch);
|
||||
rtcProvider.scheduleRoomRocketPostLaunchStatusRefresh(
|
||||
roomId: launchRoomId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_scheduleRoomRocketStatusRefreshForCurrentRoom(launchRoomId);
|
||||
|
||||
@ -85,16 +85,23 @@ class OverlayManager {
|
||||
SCFloatingMessage message, {
|
||||
required String debugLabel,
|
||||
}) {
|
||||
void enqueueNow() {
|
||||
if (_isDisposed || _isSuppressed) {
|
||||
return;
|
||||
}
|
||||
unawaited(_warmMessageImages(message));
|
||||
_messageQueue.add(message);
|
||||
_safeScheduleNext();
|
||||
}
|
||||
|
||||
if (message.type == 1) {
|
||||
enqueueNow();
|
||||
return;
|
||||
}
|
||||
|
||||
SCRoomEffectScheduler().scheduleDeferredEffect(
|
||||
debugLabel: debugLabel,
|
||||
action: () {
|
||||
if (_isDisposed || _isSuppressed) {
|
||||
return;
|
||||
}
|
||||
unawaited(_warmMessageImages(message));
|
||||
_messageQueue.add(message);
|
||||
_safeScheduleNext();
|
||||
},
|
||||
action: enqueueNow,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -16,10 +16,13 @@ import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||
class SCGiftVapSvgaManager {
|
||||
static const int giftEffectType = 0;
|
||||
static const int entryEffectType = 1;
|
||||
static const int rocketLaunchEffectType = 2;
|
||||
static const int giftEffectPriority = 100;
|
||||
static const int entryEffectPriority = 1000;
|
||||
static const int rocketLaunchEffectPriority = 2000;
|
||||
static const int _maxPendingGiftTaskCount = 24;
|
||||
static const int _maxPendingEntryTaskCount = 5;
|
||||
static const int _maxPendingRocketLaunchTaskCount = 3;
|
||||
static const int _maxConsecutiveEntryTasksBeforeGift = 2;
|
||||
static const Duration _taskWatchdogTimeout = Duration(seconds: 20);
|
||||
static const Duration _entryTaskTtl = Duration(seconds: 6);
|
||||
@ -40,6 +43,10 @@ class SCGiftVapSvgaManager {
|
||||
final SCPriorityQueue<SCVapTask> _entryQueue = SCPriorityQueue<SCVapTask>(
|
||||
(a, b) => a.compareTo(b), // 调用 SCVapTask 的 compareTo 方法
|
||||
);
|
||||
final SCPriorityQueue<SCVapTask> _rocketLaunchQueue =
|
||||
SCPriorityQueue<SCVapTask>(
|
||||
(a, b) => a.compareTo(b), // 调用 SCVapTask 的 compareTo 方法
|
||||
);
|
||||
SCGiftMp4Controller? _mp4Controller;
|
||||
SVGAAnimationController? _rsc;
|
||||
bool _play = false;
|
||||
@ -97,6 +104,11 @@ class SCGiftVapSvgaManager {
|
||||
if (type == entryEffectType) {
|
||||
return priority > entryEffectPriority ? priority : entryEffectPriority;
|
||||
}
|
||||
if (type == rocketLaunchEffectType) {
|
||||
return priority > rocketLaunchEffectPriority
|
||||
? priority
|
||||
: rocketLaunchEffectPriority;
|
||||
}
|
||||
if (priority != 0) {
|
||||
return priority;
|
||||
}
|
||||
@ -105,10 +117,15 @@ class SCGiftVapSvgaManager {
|
||||
|
||||
bool _isEntryTask(SCVapTask task) => task.type == entryEffectType;
|
||||
|
||||
int get _pendingTaskCount => _giftQueue.length + _entryQueue.length;
|
||||
bool _isRocketLaunchTask(SCVapTask task) =>
|
||||
task.type == rocketLaunchEffectType;
|
||||
|
||||
int get _pendingTaskCount =>
|
||||
_giftQueue.length + _entryQueue.length + _rocketLaunchQueue.length;
|
||||
|
||||
String get _queueSummary =>
|
||||
'entry=${_entryQueue.length} gift=${_giftQueue.length}';
|
||||
'rocket=${_rocketLaunchQueue.length} entry=${_entryQueue.length} '
|
||||
'gift=${_giftQueue.length}';
|
||||
|
||||
bool _isControllerReady(SCVapTask task) {
|
||||
if (_needsSvgaController(task.path)) {
|
||||
@ -204,7 +221,19 @@ class SCGiftVapSvgaManager {
|
||||
await _loadSvgaEntity(path);
|
||||
return;
|
||||
}
|
||||
await _ensurePlayableFilePath(path);
|
||||
final pathType = SCPathUtils.getPathType(path);
|
||||
if (pathType == PathType.asset) {
|
||||
await SCGiftMp4Controller.preloadVapLayout(
|
||||
path: path,
|
||||
sourceType: SCGiftMp4SourceType.asset,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final playablePath = await _ensurePlayableFilePath(path);
|
||||
await SCGiftMp4Controller.preloadVapLayout(
|
||||
path: playablePath,
|
||||
sourceType: SCGiftMp4SourceType.file,
|
||||
);
|
||||
}
|
||||
|
||||
Future<MovieEntity> _loadSvgaEntity(String path) async {
|
||||
@ -328,6 +357,11 @@ class SCGiftVapSvgaManager {
|
||||
}
|
||||
|
||||
void _enqueueTask(SCVapTask task) {
|
||||
if (_isRocketLaunchTask(task)) {
|
||||
_rocketLaunchQueue.add(task);
|
||||
_trimRocketLaunchQueue();
|
||||
return;
|
||||
}
|
||||
if (_isEntryTask(task)) {
|
||||
_dropExpiredEntryTasks();
|
||||
_entryQueue.add(task);
|
||||
@ -338,6 +372,27 @@ class SCGiftVapSvgaManager {
|
||||
_trimGiftQueue();
|
||||
}
|
||||
|
||||
void _trimRocketLaunchQueue() {
|
||||
while (_rocketLaunchQueue.length > _maxPendingRocketLaunchTaskCount) {
|
||||
final removedTask = _removeRocketLaunchOverflowTask();
|
||||
_completeDroppedQueuedTask(removedTask, 'trim rocket launch queue');
|
||||
}
|
||||
}
|
||||
|
||||
SCVapTask _removeRocketLaunchOverflowTask() {
|
||||
final tasks = _rocketLaunchQueue.unorderedElements.cast<SCVapTask>();
|
||||
var candidate = tasks.first;
|
||||
for (final task in tasks.skip(1)) {
|
||||
if (task.priority < candidate.priority ||
|
||||
(task.priority == candidate.priority &&
|
||||
task.createdAt.isBefore(candidate.createdAt))) {
|
||||
candidate = task;
|
||||
}
|
||||
}
|
||||
_rocketLaunchQueue.remove(candidate);
|
||||
return candidate;
|
||||
}
|
||||
|
||||
void _trimEntryQueue() {
|
||||
while (_entryQueue.length > _maxPendingEntryTaskCount) {
|
||||
final removedTask = _removeEntryOverflowTask();
|
||||
@ -394,6 +449,9 @@ class SCGiftVapSvgaManager {
|
||||
}
|
||||
|
||||
SCVapTask? _peekNextQueuedTask() {
|
||||
if (_rocketLaunchQueue.isNotEmpty) {
|
||||
return _rocketLaunchQueue.first;
|
||||
}
|
||||
_dropExpiredEntryTasks();
|
||||
if (_entryQueue.isNotEmpty) {
|
||||
final shouldYieldToGift =
|
||||
@ -408,6 +466,11 @@ class SCGiftVapSvgaManager {
|
||||
}
|
||||
|
||||
void _removeQueuedTask(SCVapTask task) {
|
||||
if (_isRocketLaunchTask(task)) {
|
||||
_rocketLaunchQueue.remove(task);
|
||||
_consecutiveEntryTaskCount = 0;
|
||||
return;
|
||||
}
|
||||
if (_isEntryTask(task)) {
|
||||
_entryQueue.remove(task);
|
||||
_consecutiveEntryTaskCount++;
|
||||
@ -755,6 +818,7 @@ class SCGiftVapSvgaManager {
|
||||
_cancelCurrentTaskWatchdog();
|
||||
_giftQueue.clear();
|
||||
_entryQueue.clear();
|
||||
_rocketLaunchQueue.clear();
|
||||
_preloadQueue.clear();
|
||||
_queuedPreloadPaths.clear();
|
||||
_activePreloadCount = 0;
|
||||
|
||||
@ -53,6 +53,13 @@ class SCGiftMp4Controller extends ChangeNotifier {
|
||||
|
||||
SCGiftMp4PlayRequest? get request => _request;
|
||||
|
||||
static Future<void> preloadVapLayout({
|
||||
required String path,
|
||||
required SCGiftMp4SourceType sourceType,
|
||||
}) async {
|
||||
await _parseVapLayout(path: path, sourceType: sourceType);
|
||||
}
|
||||
|
||||
static void _installEventsHandler() {
|
||||
if (_eventsHandlerInstalled) {
|
||||
return;
|
||||
@ -331,15 +338,44 @@ class _GiftMp4EffectViewState extends State<GiftMp4EffectView> {
|
||||
}
|
||||
}
|
||||
|
||||
const int _vapLayoutScanBytes = 512 * 1024;
|
||||
final Map<String, Map<String, Object?>?> _vapLayoutCache =
|
||||
<String, Map<String, Object?>?>{};
|
||||
final Map<String, Future<Map<String, Object?>?>> _vapLayoutTasks =
|
||||
<String, Future<Map<String, Object?>?>>{};
|
||||
|
||||
Future<Map<String, Object?>?> _parseVapLayout({
|
||||
required String path,
|
||||
required SCGiftMp4SourceType sourceType,
|
||||
}) async {
|
||||
final cacheKey = '${sourceType.name}|$path';
|
||||
if (_vapLayoutCache.containsKey(cacheKey)) {
|
||||
return _vapLayoutCache[cacheKey];
|
||||
}
|
||||
final loadingTask = _vapLayoutTasks[cacheKey];
|
||||
if (loadingTask != null) {
|
||||
return loadingTask;
|
||||
}
|
||||
final future = _parseVapLayoutUncached(path: path, sourceType: sourceType);
|
||||
_vapLayoutTasks[cacheKey] = future;
|
||||
try {
|
||||
final layout = await future;
|
||||
_vapLayoutCache[cacheKey] = layout;
|
||||
return layout;
|
||||
} finally {
|
||||
_vapLayoutTasks.remove(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, Object?>?> _parseVapLayoutUncached({
|
||||
required String path,
|
||||
required SCGiftMp4SourceType sourceType,
|
||||
}) async {
|
||||
try {
|
||||
final bytes =
|
||||
sourceType == SCGiftMp4SourceType.asset
|
||||
? (await rootBundle.load(path)).buffer.asUint8List()
|
||||
: await File(path.removePrefix('file://')).readAsBytes();
|
||||
: await _readFilePrefixBytes(path.removePrefix('file://'));
|
||||
final jsonText = _extractVapcJson(bytes);
|
||||
if (jsonText == null) {
|
||||
return null;
|
||||
@ -382,6 +418,22 @@ Future<Map<String, Object?>?> _parseVapLayout({
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> _readFilePrefixBytes(String path) async {
|
||||
final file = File(path);
|
||||
final length = await file.length();
|
||||
final readLength =
|
||||
length < _vapLayoutScanBytes ? length.toInt() : _vapLayoutScanBytes;
|
||||
if (readLength <= 0) {
|
||||
return Uint8List(0);
|
||||
}
|
||||
final randomAccessFile = await file.open();
|
||||
try {
|
||||
return await randomAccessFile.read(readLength);
|
||||
} finally {
|
||||
await randomAccessFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
String? _extractVapcJson(Uint8List bytes) {
|
||||
const marker = [0x76, 0x61, 0x70, 0x63]; // vapc
|
||||
final markerIndex = _indexOfBytes(bytes, marker, 0);
|
||||
@ -389,7 +441,9 @@ String? _extractVapcJson(Uint8List bytes) {
|
||||
return null;
|
||||
}
|
||||
final searchEnd =
|
||||
(markerIndex + marker.length + 512 * 1024).clamp(0, bytes.length).toInt();
|
||||
(markerIndex + marker.length + _vapLayoutScanBytes)
|
||||
.clamp(0, bytes.length)
|
||||
.toInt();
|
||||
for (var index = markerIndex + marker.length; index < searchEnd; index++) {
|
||||
if (bytes[index] != 0x7b) {
|
||||
continue;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user