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