Initial commit
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
||||
import 'package:flutter/foundation.dart' show debugPrint;
|
||||
import 'package:flutter/material.dart' show Colors;
|
||||
|
||||
import '../../../../core/contracts/danmaku.dart';
|
||||
import '../../../../core/media/danmaku_comment_pipeline.dart';
|
||||
|
||||
|
||||
const double _kBackwardSeekThreshold = -1.0;
|
||||
const double _kForwardSeekThreshold = 5.0;
|
||||
|
||||
|
||||
const int _kMaxFeedPerTick = 10;
|
||||
|
||||
|
||||
const double _kMaxFeedLagSec = 1.0;
|
||||
|
||||
enum DanmakuPlaybackState { detached, disabled, playing, frozen, seeking }
|
||||
|
||||
class DanmakuFeeder {
|
||||
DanmakuController? _canvas;
|
||||
List<DanmakuComment> _comments = const [];
|
||||
DanmakuPanelSettings _settings = const DanmakuPanelSettings();
|
||||
int _feedIndex = 0;
|
||||
double _lastPosSec = -1;
|
||||
DanmakuPlaybackState _state = DanmakuPlaybackState.detached;
|
||||
double? _frozenPosSec;
|
||||
|
||||
void attach(DanmakuController controller) {
|
||||
_canvas = controller;
|
||||
_state = _settings.enabled
|
||||
? DanmakuPlaybackState.frozen
|
||||
: DanmakuPlaybackState.disabled;
|
||||
}
|
||||
|
||||
void detach() {
|
||||
_canvas = null;
|
||||
_state = DanmakuPlaybackState.detached;
|
||||
}
|
||||
|
||||
|
||||
void setComments(List<DanmakuComment> commentsSortedByTime) {
|
||||
_comments = commentsSortedByTime;
|
||||
_canvas?.clear();
|
||||
_feedIndex = 0;
|
||||
_lastPosSec = -1;
|
||||
_frozenPosSec = null;
|
||||
}
|
||||
|
||||
void setPanelSettings(
|
||||
DanmakuPanelSettings settings, {
|
||||
DanmakuOption? option,
|
||||
}) {
|
||||
_settings = settings;
|
||||
if (!_settings.enabled) {
|
||||
_state = DanmakuPlaybackState.disabled;
|
||||
} else if (_state == DanmakuPlaybackState.disabled) {
|
||||
_state = _canvas == null
|
||||
? DanmakuPlaybackState.detached
|
||||
: DanmakuPlaybackState.frozen;
|
||||
}
|
||||
if (option != null) {
|
||||
try {
|
||||
_canvas?.updateOption(option);
|
||||
} catch (e) {
|
||||
debugPrint('[DanmakuFeeder] updateOption failed: $e; detaching canvas');
|
||||
_canvas = null;
|
||||
_state = DanmakuPlaybackState.detached;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void onPosition(double posSec) {
|
||||
final canvas = _canvas;
|
||||
if (canvas == null ||
|
||||
_comments.isEmpty ||
|
||||
!_settings.enabled ||
|
||||
_state != DanmakuPlaybackState.playing) {
|
||||
return;
|
||||
}
|
||||
final effectivePos = posSec - _settings.offset;
|
||||
final last = _lastPosSec;
|
||||
if (last >= 0 && _isTimelineJump(posSec - last)) {
|
||||
canvas.clear();
|
||||
_feedIndex = lowerBoundByTime(_comments, effectivePos);
|
||||
}
|
||||
_lastPosSec = posSec;
|
||||
|
||||
var fedThisTick = 0;
|
||||
while (_feedIndex < _comments.length && fedThisTick < _kMaxFeedPerTick) {
|
||||
final comment = _comments[_feedIndex];
|
||||
if (comment.time > effectivePos) break;
|
||||
_feedIndex++;
|
||||
if (effectivePos - comment.time > _kMaxFeedLagSec) continue;
|
||||
canvas.addDanmaku(_toContentItem(comment));
|
||||
fedThisTick++;
|
||||
}
|
||||
}
|
||||
|
||||
void freezeAt(double posSec) {
|
||||
final canvas = _canvas;
|
||||
if (canvas == null) {
|
||||
_state = DanmakuPlaybackState.detached;
|
||||
return;
|
||||
}
|
||||
if (!_settings.enabled) {
|
||||
_state = DanmakuPlaybackState.disabled;
|
||||
return;
|
||||
}
|
||||
_state = DanmakuPlaybackState.frozen;
|
||||
_frozenPosSec = posSec;
|
||||
_lastPosSec = posSec;
|
||||
if (canvas.running) {
|
||||
canvas.pause();
|
||||
}
|
||||
}
|
||||
|
||||
void playFrom(double posSec) {
|
||||
final canvas = _canvas;
|
||||
if (canvas == null) {
|
||||
_state = DanmakuPlaybackState.detached;
|
||||
return;
|
||||
}
|
||||
if (!_settings.enabled) {
|
||||
_state = DanmakuPlaybackState.disabled;
|
||||
return;
|
||||
}
|
||||
final frozenPos = _frozenPosSec;
|
||||
if (_state == DanmakuPlaybackState.frozen &&
|
||||
frozenPos != null &&
|
||||
posSec > frozenPos) {
|
||||
final targetIndex = lowerBoundByTime(
|
||||
_comments,
|
||||
posSec - _settings.offset,
|
||||
);
|
||||
if (targetIndex > _feedIndex) {
|
||||
_feedIndex = targetIndex;
|
||||
}
|
||||
}
|
||||
_lastPosSec = posSec;
|
||||
_frozenPosSec = null;
|
||||
_state = DanmakuPlaybackState.playing;
|
||||
if (!canvas.running) {
|
||||
canvas.resume();
|
||||
}
|
||||
}
|
||||
|
||||
void seekTo(double posSec, {bool clear = true}) {
|
||||
if (clear) _canvas?.clear();
|
||||
_feedIndex = lowerBoundByTime(_comments, posSec - _settings.offset);
|
||||
_lastPosSec = posSec;
|
||||
_frozenPosSec = null;
|
||||
if (_canvas == null) {
|
||||
_state = DanmakuPlaybackState.detached;
|
||||
} else {
|
||||
_state = _settings.enabled
|
||||
? DanmakuPlaybackState.seeking
|
||||
: DanmakuPlaybackState.disabled;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isTimelineJump(double deltaSec) {
|
||||
return deltaSec < _kBackwardSeekThreshold ||
|
||||
deltaSec > _kForwardSeekThreshold;
|
||||
}
|
||||
|
||||
DanmakuContentItem _toContentItem(DanmakuComment c) {
|
||||
final baseColor = _settings.colored ? c.color : Colors.white;
|
||||
|
||||
|
||||
final opacity = _settings.opacity.clamp(0.0, 1.0);
|
||||
final color = opacity >= 1.0
|
||||
? baseColor
|
||||
: baseColor.withValues(alpha: opacity);
|
||||
return DanmakuContentItem(
|
||||
c.text,
|
||||
color: color,
|
||||
type: switch (c.type) {
|
||||
4 when !_settings.fixedToScroll => DanmakuItemType.bottom,
|
||||
5 when !_settings.fixedToScroll => DanmakuItemType.top,
|
||||
_ => DanmakuItemType.scroll,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user