1. 播放器

This commit is contained in:
李琦
2026-08-01 17:59:07 +08:00
parent 366e3b329f
commit d824b9945e
9 changed files with 621 additions and 67 deletions

View File

@@ -39,12 +39,29 @@ let pollTimer = null
let uid = 0
/** @type {Map<string, number>} */
const lastShownAt = new Map()
/** 本批已飘过的业务弹幕 id */
const shownIds = new Set()
const itemKey = (item) => {
if (item?.id != null) return String(item.id)
return `${item?.name || ''}|${item?.content || ''}|${item?.created_at || ''}`
}
const maxItemId = () => {
let max = 0
for (const it of items.value) {
const id = Number(it?.id) || 0
if (id > max) max = id
}
return max
}
const isBatchDone = () => {
const list = items.value
if (!list.length) return true
return list.every((it) => it?.id != null && shownIds.has(Number(it.id)))
}
const isCooling = (key) => {
const t = lastShownAt.get(key)
return t != null && Date.now() - t < DEDUPE_MS
@@ -66,10 +83,15 @@ const pickNextItem = () => {
return null
}
const markShown = (item) => {
if (item?.id != null) shownIds.add(Number(item.id))
}
const buildRow = (item, { force = false } = {}) => {
const key = itemKey(item)
if (!force && isCooling(key)) return null
lastShownAt.set(key, Date.now())
markShown(item)
const tint = resolveDanmakuTint(item.color)
const track = Math.floor(Math.random() * TRACKS)
@@ -77,7 +99,6 @@ const buildRow = (item, { force = false } = {}) => {
const delay = force ? 0 : Math.random() * 0.4
const id = ++uid
// 发送者刚插入的弹幕强制「刚刚」,避免服务端时间解析偏差
const timeLabel = force
? '刚刚'
: formatRelativeTime(item.created_at || Date.now())
@@ -98,16 +119,36 @@ const buildRow = (item, { force = false } = {}) => {
}
}
const mergeItems = (incoming) => {
if (!incoming.length) return
const map = new Map(items.value.map((it) => [it.id, it]))
for (const it of incoming) {
if (it?.id != null) map.set(it.id, it)
}
items.value = Array.from(map.values()).sort((a, b) => (a.id || 0) - (b.id || 0))
}
const fetchList = async () => {
if (!props.enabled) {
items.value = []
shownIds.clear()
return
}
try {
const res = await getDanmaku()
const next = Array.isArray(res.data) ? res.data : []
items.value = next
if (next.length && cursor >= next.length) cursor = cursor % next.length
const done = isBatchDone()
const maxId = maxItemId()
if (!done && maxId > 0) {
const res = await getDanmaku({ after_id: maxId })
const next = Array.isArray(res.data) ? res.data : []
mergeItems(next)
} else {
const res = await getDanmaku()
const next = Array.isArray(res.data) ? res.data : []
items.value = next
shownIds.clear()
cursor = 0
}
if (items.value.length && cursor >= items.value.length) cursor = cursor % items.value.length
} catch {
/* keep previous items on transient failure */
}
@@ -177,6 +218,7 @@ watch(
rows.value = []
items.value = []
lastShownAt.clear()
shownIds.clear()
}
}
}
@@ -195,6 +237,7 @@ watch(
rows.value = []
items.value = []
lastShownAt.clear()
shownIds.clear()
}
}
)
@@ -236,7 +279,6 @@ defineExpose({ refresh: fetchList, pushItem })
animation-timing-function: linear;
animation-fill-mode: forwards;
backdrop-filter: blur(6px);
/* 不裁切宽度:长祝福时末尾相对时间需完整露出并随整条飞过 */
width: max-content;
max-width: none;
}

View File

@@ -26,8 +26,8 @@ const burst = (count = 3) => {
const id = ++uid
const left = 10 + Math.random() * 200
const drift = (Math.random() - 0.5) * 90
const duration = 1.25 + Math.random() * 0.7
const delay = i * 0.045
const duration = 2.8 + Math.random() * 1.0
const delay = i * 0.1
const scale = 0.8 + Math.random() * 0.55
hearts.value.push({
id,
@@ -82,7 +82,7 @@ defineExpose({ burst })
}
100% {
opacity: 0;
transform: translate3d(var(--drift, 0px), -46vh, 0) scale(var(--scale, 1));
transform: translate3d(var(--drift, 0px), -55vh, 0) scale(var(--scale, 1));
}
}
</style>