fix(reader,dashboard): handle audiobook progress reliably

This commit is contained in:
neon
2026-05-11 00:08:58 -06:00
parent 4fd91b6d1f
commit 681f684dff
2 changed files with 36 additions and 5 deletions
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch, type WatchStopHandle } from 'vue'
import { useRouter } from 'vue-router'
import {
Bookmark,
@@ -70,6 +70,7 @@ const progress = useAudioProgress(props.bookId)
// ── Queue (created lazily after files load) ───────────────────────────────────
let queue: ReturnType<typeof useAudioQueue> | null = null
let stopQueuePlayingWatch: WatchStopHandle | null = null
const isPlaying = ref(false)
const currentPosition = ref(0)
const currentFileIndex = ref(0)
@@ -100,6 +101,16 @@ function initQueue(startFileId: number, startPosition: number) {
queue.goToFile(startFileId, startPosition)
queue.setSpeed(settings.playbackSpeed.value)
queue.setVolume(settings.volume.value)
stopQueuePlayingWatch?.()
stopQueuePlayingWatch = watch(
queue.isPlaying,
(playing) => {
if (isPlaying.value !== playing) {
isPlaying.value = playing
}
},
{ immediate: true },
)
syncRefs()
currentPosition.value = startPosition
currentFileIndex.value = queue.currentIndex.value
@@ -141,7 +152,6 @@ function startTicker() {
if (!queue) return
const pos = queue.position()
currentPosition.value = pos
isPlaying.value = queue.isPlaying.value
currentFileIndex.value = queue.currentIndex.value
if (queue.isPlaying.value) {
@@ -196,6 +206,8 @@ let mounted = true
onUnmounted(() => {
mounted = false
stopQueuePlayingWatch?.()
stopQueuePlayingWatch = null
queue?.destroy()
stopTicker()
progress.flush()
@@ -15,6 +15,7 @@ import type {
import { DB } from '../../db';
import * as schema from '../../db/schema';
import {
audiobookProgress,
annotations,
authors,
bookAuthors,
@@ -63,13 +64,30 @@ export class DashboardWidgetRepository {
async getCurrentlyReadingBooks(userId: number, accessibleLibraryIds: number[]): Promise<CurrentlyReadingWidgetData> {
if (accessibleLibraryIds.length === 0) return { books: [] };
const mergedProgress = sql<number>`
coalesce(
(
case
when ${readingProgress.updatedAt} is not null
and (${audiobookProgress.updatedAt} is null or ${readingProgress.updatedAt} >= ${audiobookProgress.updatedAt})
then ${readingProgress.percentage}
else ${audiobookProgress.percentage}
end
),
${readingProgress.percentage},
${audiobookProgress.percentage},
0
)
`;
const mergedLastReadAt = sql<Date | null>`coalesce(${readingProgress.updatedAt}, ${audiobookProgress.updatedAt})`;
const rows = await this.db
.select({
bookId: books.id,
title: bookMetadata.title,
progress: readingProgress.percentage,
progress: mergedProgress,
coverSource: bookMetadata.coverSource,
lastReadAt: readingProgress.updatedAt,
lastReadAt: mergedLastReadAt,
fileId: bookFiles.id,
fileFormat: bookFiles.format,
})
@@ -78,6 +96,7 @@ export class DashboardWidgetRepository {
.innerJoin(bookMetadata, eq(bookMetadata.bookId, books.id))
.leftJoin(bookFiles, eq(bookFiles.id, books.primaryFileId))
.leftJoin(readingProgress, and(isNotNull(bookFiles.id), eq(readingProgress.bookFileId, bookFiles.id), eq(readingProgress.userId, userId)))
.leftJoin(audiobookProgress, and(eq(audiobookProgress.bookId, books.id), eq(audiobookProgress.userId, userId)))
.where(
and(
eq(userBookStatus.userId, userId),
@@ -85,7 +104,7 @@ export class DashboardWidgetRepository {
inArray(books.libraryId, accessibleLibraryIds),
),
)
.orderBy(desc(sql`coalesce(${readingProgress.updatedAt}, ${userBookStatus.updatedAt})`))
.orderBy(desc(sql`coalesce(${readingProgress.updatedAt}, ${audiobookProgress.updatedAt}, ${userBookStatus.updatedAt})`))
.limit(CURRENTLY_READING_LIMIT);
if (rows.length === 0) return { books: [] };