添加平滑消息队列

This commit is contained in:
John Smith 2019-11-28 00:52:16 +08:00
parent 3b2616c278
commit 98184e6732
3 changed files with 167 additions and 30 deletions

@ -1 +1 @@
Subproject commit efc7f3b3f8175ad05135b9819f86ed6fd561c674 Subproject commit 87651f044c5605fff08d0caffa012b4f89259353

View File

@ -20,6 +20,7 @@ export const GUARD_LEVEL_TO_TEXT = [
export const MESSAGE_TYPE_TEXT = 0 export const MESSAGE_TYPE_TEXT = 0
export const MESSAGE_TYPE_MEMBER = 1 export const MESSAGE_TYPE_MEMBER = 1
export const MESSAGE_TYPE_SUPER_CHAT = 2 export const MESSAGE_TYPE_SUPER_CHAT = 2
export const MESSAGE_TYPE_DEL = 3
// 美元 -> 人民币 汇率 // 美元 -> 人民币 汇率
const EXCHANGE_RATE = 7 const EXCHANGE_RATE = 7

View File

@ -17,7 +17,7 @@
:avatarUrl="message.avatarUrl" :title="message.title" :content="message.content" :avatarUrl="message.avatarUrl" :title="message.title" :content="message.content"
:time="message.time" :time="message.time"
></legacy-paid-message> ></legacy-paid-message>
<paid-message :key="message.id" v-else <paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_SUPER_CHAT"
class="style-scope yt-live-chat-item-list-renderer" class="style-scope yt-live-chat-item-list-renderer"
:price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName" :price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
:time="message.time" :content="message.content" :time="message.time" :content="message.content"
@ -62,11 +62,24 @@ export default {
return { return {
MESSAGE_TYPE_TEXT: constants.MESSAGE_TYPE_TEXT, MESSAGE_TYPE_TEXT: constants.MESSAGE_TYPE_TEXT,
MESSAGE_TYPE_MEMBER: constants.MESSAGE_TYPE_MEMBER, MESSAGE_TYPE_MEMBER: constants.MESSAGE_TYPE_MEMBER,
MESSAGE_TYPE_SUPER_CHAT: constants.MESSAGE_TYPE_SUPER_CHAT,
styleElement, styleElement,
messages: [], messages: [], //
paidMessages: [], paidMessages: [], //
canAutoScroll: true
smoothedMessageQueue: [], // addMessages
emitSmoothedMessageTimerId: null, // ID
enqueueIntervals: [], //
lastEnqueueTime: null, //
estimatedEnqueueInterval: null, //
atBottom: true //
}
},
computed: {
canScrollToBottom() {
return this.atBottom/* || this.allowScroll*/
} }
}, },
mounted() { mounted() {
@ -75,6 +88,10 @@ export default {
}, },
beforeDestroy() { beforeDestroy() {
document.head.removeChild(this.styleElement) document.head.removeChild(this.styleElement)
if (this.emitSmoothedMessageTimerId) {
window.clearTimeout(this.emitSmoothedMessageTimerId)
this.emitSmoothedMessageTimerId = null
}
}, },
watch: { watch: {
css(val) { css(val) {
@ -86,27 +103,7 @@ export default {
this.addMessages([message]) this.addMessages([message])
}, },
addMessages(messages) { addMessages(messages) {
for (let message of messages) { this.enqueueMessages(messages)
message = {
...message,
addTime: new Date() // Ticker
}
this.messages.push(message)
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
this.paidMessages.push(message)
}
}
if (this.messages.length > this.maxNumber) {
// https://github.com/vuejs/vue/issues/6857
this.$nextTick(() => this.messages.splice(0, this.messages.length - this.maxNumber))
}
this.$nextTick(() => {
if (this.canAutoScroll) {
this.scrollToBottom()
}
})
}, },
mergeSimilar(content) { mergeSimilar(content) {
for (let i = this.messages.length - 1; i >= 0 && i >= this.messages.length - 5; i--) { for (let i = this.messages.length - 1; i >= 0 && i >= this.messages.length - 5; i--) {
@ -129,6 +126,141 @@ export default {
return false return false
}, },
delMessage(id) { delMessage(id) {
this.delMessages([id])
},
delMessages(ids) {
this.enqueueMessages(ids.map(id => {
return {
id: id,
type: constants.MESSAGE_TYPE_DEL
}
}))
},
clearMessages() {
this.messages = []
this.paidMessages = []
},
enqueueMessages(messages) {
if (this.lastEnqueueTime) {
let interval = new Date() - this.lastEnqueueTime
if (interval > 0) {
this.enqueueIntervals.push(interval)
if (this.enqueueIntervals.length > 5) {
this.enqueueIntervals.splice(0, this.enqueueIntervals.length - 5)
}
this.estimatedEnqueueInterval = Math.max(...this.enqueueIntervals)
}
}
this.lastEnqueueTime = new Date()
//
let messageGroup = []
for (let message of messages) {
messageGroup.push(message)
if (message.type !== constants.MESSAGE_TYPE_DEL) {
this.smoothedMessageQueue.push(messageGroup)
messageGroup = []
}
}
if (messageGroup.length > 0) {
this.smoothedMessageQueue.push(messageGroup)
}
if (!this.emitSmoothedMessageTimerId) {
this.emitSmoothedMessageTimerId = window.setTimeout(this.emitSmoothedMessages)
}
},
emitSmoothedMessages() {
this.emitSmoothedMessageTimerId = null
if (this.smoothedMessageQueue.length <= 0) {
return
}
//
let estimatedNextEnqueueRemainTime = 10 * 1000
if (this.estimatedEnqueueInterval) {
estimatedNextEnqueueRemainTime = Math.max(this.lastEnqueueTime - new Date() + this.estimatedEnqueueInterval, 1)
}
// 80ms/
let groupNumToEmit
if (this.smoothedMessageQueue.length < estimatedNextEnqueueRemainTime / 80) {
// 1
groupNumToEmit = 1
} else {
// 1
groupNumToEmit = Math.ceil(this.smoothedMessageQueue.length / (estimatedNextEnqueueRemainTime / 80))
}
let messageGroups = this.smoothedMessageQueue.splice(0, groupNumToEmit)
let mergedGroup = []
for (let messageGroup of messageGroups) {
for (let message of messageGroup) {
mergedGroup.push(message)
}
}
this.handleMessageGroup(mergedGroup)
if (this.smoothedMessageQueue.length <= 0) {
return
}
let sleepTime
if (groupNumToEmit == 1) {
// 便80-1000ms
sleepTime = estimatedNextEnqueueRemainTime / this.smoothedMessageQueue.length
sleepTime *= 0.5 + Math.random()
if (sleepTime > 1000) {
sleepTime = 1000
} else if (sleepTime < 80) {
sleepTime = 80
}
} else {
//
sleepTime = 80
}
this.emitSmoothedMessageTimerId = window.setTimeout(this.emitSmoothedMessages, sleepTime)
},
handleMessageGroup(messageGroup) {
if (messageGroup.length <= 0) {
return
}
for (let message of messageGroup) {
switch (message.type) {
case constants.MESSAGE_TYPE_TEXT:
case constants.MESSAGE_TYPE_MEMBER:
case constants.MESSAGE_TYPE_SUPER_CHAT:
this.handleAddMessage(message)
break
case constants.MESSAGE_TYPE_DEL:
this.handleDelMessage(message.id)
break
}
}
if (this.messages.length > this.maxNumber) {
// https://github.com/vuejs/vue/issues/6857
this.$nextTick(() => this.messages.splice(0, this.messages.length - this.maxNumber))
}
this.$nextTick(() => {
if (this.canScrollToBottom) {
this.scrollToBottom()
}
})
},
handleAddMessage(message) {
message = {
...message,
addTime: new Date() // Ticker
}
this.messages.push(message)
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
this.paidMessages.push(message)
}
},
handleDelMessage(message) {
let id = message.id
for (let arr of [this.messages, this.paidMessages]) { for (let arr of [this.messages, this.paidMessages]) {
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
if (arr[i].id === id) { if (arr[i].id === id) {
@ -138,16 +270,20 @@ export default {
} }
} }
}, },
clearMessages() {
this.messages = [] maybeScrollToBottom() {
this.paidMessages = [] if (this.canScrollToBottom) {
this.scrollToBottom()
}
}, },
scrollToBottom() { scrollToBottom() {
this.$refs.scroller.scrollTop = this.$refs.scroller.scrollHeight this.$refs.scroller.scrollTop = this.$refs.scroller.scrollHeight
this.atBottom = true
}, },
onScroll() { onScroll() {
let scroller = this.$refs.scroller let scroller = this.$refs.scroller
this.canAutoScroll = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < SCROLLED_TO_BOTTOM_EPSILON this.atBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < SCROLLED_TO_BOTTOM_EPSILON
// this.flushActiveItems()
} }
} }
} }