mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-08 18:31:43 +00:00
modified
This commit is contained in:
parent
0107cb4782
commit
6e7ba0e51c
3 changed files with 287 additions and 83 deletions
|
|
@ -4,40 +4,60 @@
|
||||||
<div class="absolute -top-10 lg:top-0 right-0 lg:right-2 flex items-center h-full">
|
<div class="absolute -top-10 lg:top-0 right-0 lg:right-2 flex items-center h-full">
|
||||||
<controls-playback-speed-control v-model="playbackRate" @input="setPlaybackRate" @change="playbackRateChanged" :playbackRateIncrementDecrement="playbackRateIncrementDecrement" class="mx-2 block" />
|
<controls-playback-speed-control v-model="playbackRate" @input="setPlaybackRate" @change="playbackRateChanged" :playbackRateIncrementDecrement="playbackRateIncrementDecrement" class="mx-2 block" />
|
||||||
|
|
||||||
<ui-tooltip direction="bottom" :text="$strings.LabelVolume">
|
<ui-tooltip direction="bottom" :text="$strings?.LabelVolume || 'Volume'">
|
||||||
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden sm:block" />
|
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden sm:block" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="!hideSleepTimer" direction="top" :text="$strings.LabelSleepTimer">
|
<!-- ==== 渐降音量按钮 ==== -->
|
||||||
<button :aria-label="$strings.LabelSleepTimer" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showSleepTimer')">
|
<ui-tooltip direction="top" :text="fadeActive ? fadeTooltipCancel : $strings?.LabelStartFadeVolume || 'Start volume fade'">
|
||||||
|
<button :aria-label="fadeActive ? 'Cancel fade' : 'Start fade'" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="toggleFadeDialog">
|
||||||
|
<template v-if="!fadeActive">
|
||||||
|
<span class="material-symbols text-2xl">timelapse</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="material-symbols text-lg text-warning">volume_down</span>
|
||||||
|
<p class="text-sm sm:text-lg text-warning font-semibold text-center px-0.5 sm:pb-0.5 sm:min-w-8">
|
||||||
|
{{ fadeRemainingPretty }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
|
<!-- ==== /渐降音量按钮 ==== -->
|
||||||
|
|
||||||
|
<ui-tooltip v-if="!hideSleepTimer" direction="top" :text="$strings?.LabelSleepTimer || 'Sleep timer'">
|
||||||
|
<button :aria-label="$strings?.LabelSleepTimer || 'Sleep timer'" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showSleepTimer')">
|
||||||
<span v-if="!sleepTimerSet" class="material-symbols text-2xl">snooze</span>
|
<span v-if="!sleepTimerSet" class="material-symbols text-2xl">snooze</span>
|
||||||
<div v-else class="flex items-center">
|
<div v-else class="flex items-center">
|
||||||
<span class="material-symbols text-lg text-warning">snooze</span>
|
<span class="material-symbols text-lg text-warning">snooze</span>
|
||||||
<p class="text-sm sm:text-lg text-warning font-semibold text-center px-0.5 sm:pb-0.5 sm:min-w-8">{{ sleepTimerRemainingString }}</p>
|
<p class="text-sm sm:text-lg text-warning font-semibold text-center px-0.5 sm:pb-0.5 sm:min-w-8">
|
||||||
|
{{ sleepTimerRemainingString }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="!isPodcast && !hideBookmarks" direction="top" :text="$strings.LabelViewBookmarks">
|
<ui-tooltip v-if="!isPodcast && !hideBookmarks" direction="top" :text="$strings?.LabelViewBookmarks || 'Bookmarks'">
|
||||||
<button :aria-label="$strings.LabelViewBookmarks" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showBookmarks')">
|
<button :aria-label="$strings?.LabelViewBookmarks || 'Bookmarks'" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showBookmarks')">
|
||||||
<span class="material-symbols text-2xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span>
|
<span class="material-symbols text-2xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="chapters.length" direction="top" :text="$strings.LabelViewChapters">
|
<ui-tooltip v-if="chapters.length" direction="top" :text="$strings?.LabelViewChapters || 'Chapters'">
|
||||||
<button :aria-label="$strings.LabelViewChapters" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="showChapters">
|
<button :aria-label="$strings?.LabelViewChapters || 'Chapters'" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="showChapters">
|
||||||
<span class="material-symbols text-2xl">format_list_bulleted</span>
|
<span class="material-symbols text-2xl">format_list_bulleted</span>
|
||||||
</button>
|
</button>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="playerQueueItems.length" direction="top" :text="$strings.LabelViewQueue">
|
<ui-tooltip v-if="playerQueueItems.length" direction="top" :text="$strings?.LabelViewQueue || 'Queue'">
|
||||||
<button :aria-label="$strings.LabelViewQueue" class="outline-hidden text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerQueueItems')">
|
<button :aria-label="$strings?.LabelViewQueue || 'Queue'" class="outline-hidden text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerQueueItems')">
|
||||||
<span class="material-symbols text-2.5xl sm:text-3xl">playlist_play</span>
|
<span class="material-symbols text-2.5xl sm:text-3xl">playlist_play</span>
|
||||||
</button>
|
</button>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip direction="top" :text="$strings.LabelViewPlayerSettings">
|
<ui-tooltip direction="top" :text="$strings?.LabelViewPlayerSettings || 'Player settings'">
|
||||||
<button :aria-label="$strings.LabelViewPlayerSettings" class="outline-hidden text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="showPlayerSettings">
|
<button :aria-label="$strings?.LabelViewPlayerSettings || 'Player settings'" class="outline-hidden text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="showPlayerSettings">
|
||||||
<span class="material-symbols text-2xl sm:text-2.5xl">settings_slow_motion</span>
|
<span class="material-symbols text-2xl sm:text-2.5xl">settings_slow_motion</span>
|
||||||
</button>
|
</button>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
@ -55,7 +75,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute left-1/2 transform -translate-x-1/2">
|
<div class="absolute left-1/2 transform -translate-x-1/2">
|
||||||
<p class="text-xs sm:text-sm text-gray-300 pt-0.5 px-2 truncate">
|
<p class="text-xs sm:text-sm text-gray-300 pt-0.5 px-2 truncate">
|
||||||
{{ currentChapterName }} <span v-if="useChapterTrack" class="text-xs text-gray-400"> ({{ $getString('LabelPlayerChapterNumberMarker', [currentChapterIndex + 1, chapters.length]) }})</span>
|
{{ currentChapterName }}
|
||||||
|
<span v-if="useChapterTrack" class="text-xs text-gray-400"> ({{ $getString('LabelPlayerChapterNumberMarker', [currentChapterIndex + 1, chapters.length]) }}) </span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grow flex items-center justify-end">
|
<div class="grow flex items-center justify-end">
|
||||||
|
|
@ -74,15 +95,9 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
loading: Boolean,
|
loading: Boolean,
|
||||||
paused: Boolean,
|
paused: Boolean,
|
||||||
chapters: {
|
chapters: { type: Array, default: () => [] },
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
currentChapter: Object,
|
currentChapter: Object,
|
||||||
bookmarks: {
|
bookmarks: { type: Array, default: () => [] },
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
sleepTimerSet: Boolean,
|
sleepTimerSet: Boolean,
|
||||||
sleepTimerRemaining: Number,
|
sleepTimerRemaining: Number,
|
||||||
sleepTimerType: String,
|
sleepTimerType: String,
|
||||||
|
|
@ -100,7 +115,14 @@ export default {
|
||||||
showChaptersModal: false,
|
showChaptersModal: false,
|
||||||
showPlayerSettingsModal: false,
|
showPlayerSettingsModal: false,
|
||||||
currentTime: 0,
|
currentTime: 0,
|
||||||
duration: 0
|
duration: 0,
|
||||||
|
|
||||||
|
// 渐降音量状态
|
||||||
|
fadeActive: false,
|
||||||
|
fadeMsTotal: 0,
|
||||||
|
fadeMsLeft: 0,
|
||||||
|
fadeTimer: null,
|
||||||
|
fadeStartVolume: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -114,38 +136,40 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sleepTimerRemainingString() {
|
sleepTimerRemainingString() {
|
||||||
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER) {
|
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER) return 'EoC'
|
||||||
return 'EoC'
|
const rounded = Math.round(this.sleepTimerRemaining || 0)
|
||||||
} else {
|
if (rounded < 90) return `${rounded}s`
|
||||||
var rounded = Math.round(this.sleepTimerRemaining)
|
const m = Math.round(rounded / 60)
|
||||||
if (rounded < 90) {
|
if (m <= 90) return `${m}m`
|
||||||
return `${rounded}s`
|
const h = Math.round(m / 60)
|
||||||
}
|
return `${h}h`
|
||||||
var minutesRounded = Math.round(rounded / 60)
|
|
||||||
if (minutesRounded <= 90) {
|
|
||||||
return `${minutesRounded}m`
|
|
||||||
}
|
|
||||||
var hoursRounded = Math.round(minutesRounded / 60)
|
|
||||||
return `${hoursRounded}h`
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fadeTooltipCancel() {
|
||||||
|
return (this.$strings && this.$strings.LabelCancelFadeVolume) || 'Cancel volume fade'
|
||||||
|
},
|
||||||
|
fadeRemainingPretty() {
|
||||||
|
const sec = Math.max(0, Math.ceil(this.fadeMsLeft / 1000))
|
||||||
|
if (sec < 90) return `${sec}s`
|
||||||
|
const m = Math.round(sec / 60)
|
||||||
|
if (m <= 90) return `${m}m`
|
||||||
|
const h = Math.round(m / 60)
|
||||||
|
return `${h}h`
|
||||||
|
},
|
||||||
|
|
||||||
timeRemaining() {
|
timeRemaining() {
|
||||||
if (this.useChapterTrack && this.currentChapter) {
|
if (this.useChapterTrack && this.currentChapter) {
|
||||||
var currChapTime = this.currentTime - this.currentChapter.start
|
const curr = this.currentTime - this.currentChapter.start
|
||||||
return (this.currentChapterDuration - currChapTime) / this.playbackRate
|
return (this.currentChapterDuration - curr) / this.playbackRate
|
||||||
}
|
}
|
||||||
return (this.duration - this.currentTime) / this.playbackRate
|
return (this.duration - this.currentTime) / this.playbackRate
|
||||||
},
|
},
|
||||||
timeRemainingPretty() {
|
timeRemainingPretty() {
|
||||||
if (this.timeRemaining < 0) {
|
return this.timeRemaining < 0 ? this.$secondsToTimestamp(-this.timeRemaining) : '-' + this.$secondsToTimestamp(this.timeRemaining)
|
||||||
return this.$secondsToTimestamp(this.timeRemaining * -1)
|
|
||||||
}
|
|
||||||
return '-' + this.$secondsToTimestamp(this.timeRemaining)
|
|
||||||
},
|
},
|
||||||
progressPercent() {
|
progressPercent() {
|
||||||
const duration = this.useChapterTrack ? this.currentChapterDuration : this.duration
|
const duration = this.useChapterTrack ? this.currentChapterDuration : this.duration
|
||||||
const time = this.useChapterTrack ? Math.max(this.currentTime - this.currentChapterStart) : this.currentTime
|
const time = this.useChapterTrack ? Math.max(0, this.currentTime - this.currentChapterStart) : this.currentTime
|
||||||
|
|
||||||
if (!duration) return 0
|
if (!duration) return 0
|
||||||
return Math.round((100 * time) / duration)
|
return Math.round((100 * time) / duration)
|
||||||
},
|
},
|
||||||
|
|
@ -153,12 +177,10 @@ export default {
|
||||||
return this.currentChapter?.title || ''
|
return this.currentChapter?.title || ''
|
||||||
},
|
},
|
||||||
currentChapterDuration() {
|
currentChapterDuration() {
|
||||||
if (!this.currentChapter) return 0
|
return this.currentChapter ? this.currentChapter.end - this.currentChapter.start : 0
|
||||||
return this.currentChapter.end - this.currentChapter.start
|
|
||||||
},
|
},
|
||||||
currentChapterStart() {
|
currentChapterStart() {
|
||||||
if (!this.currentChapter) return 0
|
return this.currentChapter ? this.currentChapter.start : 0
|
||||||
return this.currentChapter.start
|
|
||||||
},
|
},
|
||||||
isFullscreen() {
|
isFullscreen() {
|
||||||
return this.$store.state.playerIsFullscreen
|
return this.$store.state.playerIsFullscreen
|
||||||
|
|
@ -175,14 +197,80 @@ export default {
|
||||||
return this.$store.state.playerQueueItems || []
|
return this.$store.state.playerQueueItems || []
|
||||||
},
|
},
|
||||||
useChapterTrack() {
|
useChapterTrack() {
|
||||||
const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false
|
const v = this.$store.getters['user/getUserSetting']('useChapterTrack') || false
|
||||||
return this.chapters.length ? _useChapterTrack : false
|
return this.chapters.length ? v : false
|
||||||
},
|
},
|
||||||
playbackRateIncrementDecrement() {
|
playbackRateIncrementDecrement() {
|
||||||
return this.$store.getters['user/getUserSetting']('playbackRateIncrementDecrement')
|
return this.$store.getters['user/getUserSetting']('playbackRateIncrementDecrement')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/* ===== 渐降音量:UI入口 ===== */
|
||||||
|
toggleFadeDialog() {
|
||||||
|
if (this.fadeActive) {
|
||||||
|
this.cancelFade()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const defVal = '15'
|
||||||
|
const msg = (this.$strings && this.$strings.PromptFadeMinutes) || 'Fade to silence in how many minutes?'
|
||||||
|
const v = window.prompt(msg, defVal)
|
||||||
|
if (v == null) return
|
||||||
|
const minutes = Number(v)
|
||||||
|
if (!isFinite(minutes) || minutes <= 0) {
|
||||||
|
this.$toast && this.$toast.error('Invalid minutes')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.startFade(minutes * 60 * 1000)
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ===== 渐降音量:核心逻辑 ===== */
|
||||||
|
startFade(msTotal) {
|
||||||
|
// 清理旧的
|
||||||
|
this.cancelFade(false)
|
||||||
|
|
||||||
|
this.fadeMsTotal = Math.max(1000, msTotal)
|
||||||
|
this.fadeMsLeft = this.fadeMsTotal
|
||||||
|
this.fadeStartVolume = Math.max(0, Math.min(1, this.volume))
|
||||||
|
this.fadeActive = true
|
||||||
|
|
||||||
|
// 先同步一次
|
||||||
|
this.applyFade(0)
|
||||||
|
|
||||||
|
const tickMs = 500
|
||||||
|
this.fadeTimer = setInterval(() => {
|
||||||
|
this.fadeMsLeft = Math.max(0, this.fadeMsLeft - tickMs)
|
||||||
|
const elapsed = this.fadeMsTotal - this.fadeMsLeft
|
||||||
|
this.applyFade(elapsed)
|
||||||
|
|
||||||
|
if (this.fadeMsLeft <= 0) {
|
||||||
|
this.setVolume(0)
|
||||||
|
this.cancelFade(false)
|
||||||
|
}
|
||||||
|
}, tickMs)
|
||||||
|
},
|
||||||
|
|
||||||
|
applyFade(elapsedMs) {
|
||||||
|
const t = Math.min(1, Math.max(0, elapsedMs / this.fadeMsTotal))
|
||||||
|
const vol = +(this.fadeStartVolume * (1 - t)).toFixed(4)
|
||||||
|
this.volume = vol
|
||||||
|
this.setVolume(vol)
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelFade(toast = true) {
|
||||||
|
if (this.fadeTimer) {
|
||||||
|
clearInterval(this.fadeTimer)
|
||||||
|
this.fadeTimer = null
|
||||||
|
}
|
||||||
|
if (this.fadeActive && toast && this.$toast) {
|
||||||
|
const msg = (this.$strings && this.$strings.ToastFadeCancelled) || 'Volume fade cancelled'
|
||||||
|
this.$toast.info(msg)
|
||||||
|
}
|
||||||
|
this.fadeActive = false
|
||||||
|
this.fadeMsTotal = 0
|
||||||
|
this.fadeMsLeft = 0
|
||||||
|
},
|
||||||
|
|
||||||
|
// ===== 现有播放器逻辑 =====
|
||||||
toggleFullscreen(isFullscreen) {
|
toggleFullscreen(isFullscreen) {
|
||||||
this.$store.commit('setPlayerIsFullscreen', isFullscreen)
|
this.$store.commit('setPlayerIsFullscreen', isFullscreen)
|
||||||
},
|
},
|
||||||
|
|
@ -203,6 +291,7 @@ export default {
|
||||||
jumpForward() {
|
jumpForward() {
|
||||||
this.$emit('jumpForward')
|
this.$emit('jumpForward')
|
||||||
},
|
},
|
||||||
|
|
||||||
increaseVolume() {
|
increaseVolume() {
|
||||||
if (this.volume >= 1) return
|
if (this.volume >= 1) return
|
||||||
this.volume = Math.min(1, this.volume + 0.1)
|
this.volume = Math.min(1, this.volume + 0.1)
|
||||||
|
|
@ -221,6 +310,7 @@ export default {
|
||||||
this.$refs.volumeControl.toggleMute()
|
this.$refs.volumeControl.toggleMute()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
increasePlaybackRate() {
|
increasePlaybackRate() {
|
||||||
if (this.playbackRate >= 10) return
|
if (this.playbackRate >= 10) return
|
||||||
this.playbackRate = Number((this.playbackRate + this.playbackRateIncrementDecrement || 0.1).toFixed(2))
|
this.playbackRate = Number((this.playbackRate + this.playbackRateIncrementDecrement || 0.1).toFixed(2))
|
||||||
|
|
@ -247,12 +337,10 @@ export default {
|
||||||
setUseChapterTrack() {
|
setUseChapterTrack() {
|
||||||
this.useChapterTrack = !this.useChapterTrack
|
this.useChapterTrack = !this.useChapterTrack
|
||||||
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
||||||
|
|
||||||
this.$store.dispatch('user/updateUserSettings', { useChapterTrack: this.useChapterTrack })
|
this.$store.dispatch('user/updateUserSettings', { useChapterTrack: this.useChapterTrack })
|
||||||
this.updateTimestamp()
|
this.updateTimestamp()
|
||||||
},
|
},
|
||||||
checkUpdateChapterTrack() {
|
checkUpdateChapterTrack() {
|
||||||
// Changing media in player may not have chapters
|
|
||||||
if (!this.chapters.length && this.useChapterTrack) {
|
if (!this.chapters.length && this.useChapterTrack) {
|
||||||
this.useChapterTrack = false
|
this.useChapterTrack = false
|
||||||
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
||||||
|
|
@ -265,21 +353,19 @@ export default {
|
||||||
this.seek(0)
|
this.seek(0)
|
||||||
},
|
},
|
||||||
prevChapter() {
|
prevChapter() {
|
||||||
if (!this.currentChapter || this.currentChapterIndex === 0) {
|
if (!this.currentChapter || this.currentChapterIndex === 0) return this.restart()
|
||||||
return this.restart()
|
const t = this.currentTime - this.currentChapter.start
|
||||||
}
|
if (t <= 3 && this.chapters[this.currentChapterIndex - 1]) {
|
||||||
var timeInCurrentChapter = this.currentTime - this.currentChapter.start
|
const prev = this.chapters[this.currentChapterIndex - 1]
|
||||||
if (timeInCurrentChapter <= 3 && this.chapters[this.currentChapterIndex - 1]) {
|
this.seek(prev.start)
|
||||||
var prevChapter = this.chapters[this.currentChapterIndex - 1]
|
|
||||||
this.seek(prevChapter.start)
|
|
||||||
} else {
|
} else {
|
||||||
this.seek(this.currentChapter.start)
|
this.seek(this.currentChapter.start)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goToNext() {
|
goToNext() {
|
||||||
if (this.hasNextChapter) {
|
if (this.hasNextChapter) {
|
||||||
const nextChapter = this.chapters[this.currentChapterIndex + 1]
|
const next = this.chapters[this.currentChapterIndex + 1]
|
||||||
this.seek(nextChapter.start)
|
this.seek(next.start)
|
||||||
} else if (this.hasNextItemInQueue) {
|
} else if (this.hasNextItemInQueue) {
|
||||||
this.$emit('nextItemInQueue')
|
this.$emit('nextItemInQueue')
|
||||||
}
|
}
|
||||||
|
|
@ -288,26 +374,22 @@ export default {
|
||||||
if (this.$refs.trackbar) this.$refs.trackbar.setPercentageReady(1)
|
if (this.$refs.trackbar) this.$refs.trackbar.setPercentageReady(1)
|
||||||
},
|
},
|
||||||
setChunksReady(chunks, numSegments) {
|
setChunksReady(chunks, numSegments) {
|
||||||
var largestSeg = 0
|
let largestSeg = 0
|
||||||
for (let i = 0; i < chunks.length; i++) {
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
var chunk = chunks[i]
|
const c = chunks[i]
|
||||||
if (typeof chunk === 'string') {
|
if (typeof c === 'string') {
|
||||||
var chunkRange = chunk.split('-').map((c) => Number(c))
|
const r = c.split('-').map((x) => Number(x))
|
||||||
if (chunkRange.length < 2) continue
|
if (r.length >= 2 && r[1] > largestSeg) largestSeg = r[1]
|
||||||
if (chunkRange[1] > largestSeg) largestSeg = chunkRange[1]
|
} else if (c > largestSeg) {
|
||||||
} else if (chunk > largestSeg) {
|
largestSeg = c
|
||||||
largestSeg = chunk
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var percentageReady = largestSeg / numSegments
|
const p = largestSeg / numSegments
|
||||||
if (this.$refs.trackbar) this.$refs.trackbar.setPercentageReady(percentageReady)
|
if (this.$refs.trackbar) this.$refs.trackbar.setPercentageReady(p)
|
||||||
},
|
},
|
||||||
updateTimestamp() {
|
updateTimestamp() {
|
||||||
const ts = this.$refs.currentTimestamp
|
const ts = this.$refs.currentTimestamp
|
||||||
if (!ts) {
|
if (!ts) return console.error('No timestamp el')
|
||||||
console.error('No timestamp el')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const time = this.useChapterTrack ? Math.max(0, this.currentTime - this.currentChapterStart) : this.currentTime
|
const time = this.useChapterTrack ? Math.max(0, this.currentTime - this.currentChapterStart) : this.currentTime
|
||||||
ts.innerText = this.$secondsToTimestamp(time / this.playbackRate)
|
ts.innerText = this.$secondsToTimestamp(time / this.playbackRate)
|
||||||
},
|
},
|
||||||
|
|
@ -323,7 +405,6 @@ export default {
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1
|
this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1
|
||||||
|
|
||||||
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
||||||
this.setPlaybackRate(this.playbackRate)
|
this.setPlaybackRate(this.playbackRate)
|
||||||
},
|
},
|
||||||
|
|
@ -337,7 +418,6 @@ export default {
|
||||||
this.toggleFullscreen(false)
|
this.toggleFullscreen(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.loading) return
|
if (this.loading) return
|
||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
},
|
},
|
||||||
|
|
@ -357,12 +437,12 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$eventBus.$on('player-hotkey', this.hotkey)
|
this.$eventBus.$on('player-hotkey', this.hotkey)
|
||||||
this.$eventBus.$on('user-settings', this.settingsUpdated)
|
this.$eventBus.$on('user-settings', this.settingsUpdated)
|
||||||
|
|
||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.$eventBus.$off('player-hotkey', this.hotkey)
|
this.$eventBus.$off('player-hotkey', this.hotkey)
|
||||||
this.$eventBus.$off('user-settings', this.settingsUpdated)
|
this.$eventBus.$off('user-settings', this.settingsUpdated)
|
||||||
|
this.cancelFade(false) // 清理定时器
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
131
package-lock.json
generated
131
package-lock.json
generated
|
|
@ -17,6 +17,7 @@
|
||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.0.3",
|
||||||
|
"mysql2": "^3.14.2",
|
||||||
"node-unrar-js": "^2.0.2",
|
"node-unrar-js": "^2.0.2",
|
||||||
"nodemailer": "^6.9.13",
|
"nodemailer": "^6.9.13",
|
||||||
"openid-client": "^5.6.1",
|
"openid-client": "^5.6.1",
|
||||||
|
|
@ -24,7 +25,7 @@
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"sequelize": "^6.35.2",
|
"sequelize": "^6.37.7",
|
||||||
"socket.io": "^4.5.4",
|
"socket.io": "^4.5.4",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"ssrf-req-filter": "^1.1.0",
|
"ssrf-req-filter": "^1.1.0",
|
||||||
|
|
@ -907,6 +908,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/aws-ssl-profiles": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||||
|
|
@ -1557,6 +1567,15 @@
|
||||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
|
@ -2121,6 +2140,15 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/generate-function": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-property": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gensync": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
|
|
@ -2581,6 +2609,12 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-property": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-stream": {
|
"node_modules/is-stream": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
|
|
@ -2988,6 +3022,12 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/loupe": {
|
"node_modules/loupe": {
|
||||||
"version": "2.3.7",
|
"version": "2.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
|
||||||
|
|
@ -3005,6 +3045,21 @@
|
||||||
"node": "14 || >=16.14"
|
"node": "14 || >=16.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lru.min": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.0.0",
|
||||||
|
"deno": ">=1.30.0",
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wellwelwel"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/make-dir": {
|
"node_modules/make-dir": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||||
|
|
@ -3541,6 +3596,59 @@
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/mysql2": {
|
||||||
|
"version": "3.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.2.tgz",
|
||||||
|
"integrity": "sha512-YD6mZMeoypmheHT6b2BrVmQFvouEpRICuvPIREulx2OvP1xAxxeqkMQqZSTBefv0PiOBKGYFa2zQtY+gf/4eQw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"aws-ssl-profiles": "^1.1.1",
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"generate-function": "^2.3.1",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"long": "^5.2.1",
|
||||||
|
"lru.min": "^1.0.0",
|
||||||
|
"named-placeholders": "^1.1.3",
|
||||||
|
"seq-queue": "^0.0.5",
|
||||||
|
"sqlstring": "^2.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mysql2/node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/named-placeholders": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^7.14.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/named-placeholders/node_modules/lru-cache": {
|
||||||
|
"version": "7.18.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||||
|
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
|
||||||
|
|
@ -4493,16 +4601,22 @@
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/seq-queue": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||||
|
},
|
||||||
"node_modules/sequelize": {
|
"node_modules/sequelize": {
|
||||||
"version": "6.35.2",
|
"version": "6.37.7",
|
||||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.35.2.tgz",
|
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.7.tgz",
|
||||||
"integrity": "sha512-EdzLaw2kK4/aOnWQ7ed/qh3B6/g+1DvmeXr66RwbcqSm/+QRS9X0LDI5INBibsy4eNJHWIRPo3+QK0zL+IPBHg==",
|
"integrity": "sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/sequelize"
|
"url": "https://opencollective.com/sequelize"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/debug": "^4.1.8",
|
"@types/debug": "^4.1.8",
|
||||||
"@types/validator": "^13.7.17",
|
"@types/validator": "^13.7.17",
|
||||||
|
|
@ -4976,6 +5090,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sqlstring": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ssrf-req-filter": {
|
"node_modules/ssrf-req-filter": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ssrf-req-filter/-/ssrf-req-filter-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ssrf-req-filter/-/ssrf-req-filter-1.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@
|
||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.0.3",
|
||||||
|
"mysql2": "^3.14.2",
|
||||||
"node-unrar-js": "^2.0.2",
|
"node-unrar-js": "^2.0.2",
|
||||||
"nodemailer": "^6.9.13",
|
"nodemailer": "^6.9.13",
|
||||||
"openid-client": "^5.6.1",
|
"openid-client": "^5.6.1",
|
||||||
|
|
@ -52,7 +53,7 @@
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"sequelize": "^6.35.2",
|
"sequelize": "^6.37.7",
|
||||||
"socket.io": "^4.5.4",
|
"socket.io": "^4.5.4",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"ssrf-req-filter": "^1.1.0",
|
"ssrf-req-filter": "^1.1.0",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue