mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-05-25 12:51:31 +00:00
Merge 212734a92f into 47ea6b5092
This commit is contained in:
commit
07f16f9681
4 changed files with 145 additions and 6 deletions
|
|
@ -528,7 +528,11 @@ export default {
|
||||||
if (this.$refs.audioPlayer) this.$refs.audioPlayer.checkUpdateChapterTrack()
|
if (this.$refs.audioPlayer) this.$refs.audioPlayer.checkUpdateChapterTrack()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.playerHandler.load(libraryItem, episodeId, true, this.currentPlaybackRate, payload.startTime)
|
// Resolve per-book playback rate for the new item, falling back to current rate
|
||||||
|
const mediaProgress = this.$store.getters['user/getUserMediaProgress'](libraryItemId, episodeId)
|
||||||
|
const playbackRate = mediaProgress?.playbackRate || this.currentPlaybackRate
|
||||||
|
|
||||||
|
this.playerHandler.load(libraryItem, episodeId, true, playbackRate, payload.startTime)
|
||||||
},
|
},
|
||||||
pauseItem() {
|
pauseItem() {
|
||||||
this.playerHandler.pause()
|
this.playerHandler.pause()
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,12 @@ export default {
|
||||||
useChapterTrack() {
|
useChapterTrack() {
|
||||||
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
||||||
this.updateTimestamp()
|
this.updateTimestamp()
|
||||||
|
},
|
||||||
|
'$store.state.streamLibraryItem'() {
|
||||||
|
this.initPlaybackRate()
|
||||||
|
},
|
||||||
|
'$store.state.streamEpisodeId'() {
|
||||||
|
this.initPlaybackRate()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -224,18 +230,27 @@ export default {
|
||||||
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))
|
||||||
this.setPlaybackRate(this.playbackRate)
|
this.playbackRateChanged(this.playbackRate)
|
||||||
},
|
},
|
||||||
decreasePlaybackRate() {
|
decreasePlaybackRate() {
|
||||||
if (this.playbackRate <= 0.5) return
|
if (this.playbackRate <= 0.5) return
|
||||||
this.playbackRate = Number((this.playbackRate - this.playbackRateIncrementDecrement || 0.1).toFixed(2))
|
this.playbackRate = Number((this.playbackRate - this.playbackRateIncrementDecrement || 0.1).toFixed(2))
|
||||||
this.setPlaybackRate(this.playbackRate)
|
this.playbackRateChanged(this.playbackRate)
|
||||||
},
|
},
|
||||||
playbackRateChanged(playbackRate) {
|
playbackRateChanged(playbackRate) {
|
||||||
this.setPlaybackRate(playbackRate)
|
this.setPlaybackRate(playbackRate)
|
||||||
this.$store.dispatch('user/updateUserSettings', { playbackRate }).catch((err) => {
|
this.$store.dispatch('user/updateUserSettings', { playbackRate }).catch((err) => {
|
||||||
console.error('Failed to update settings', err)
|
console.error('Failed to update settings', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Save per-book playback rate to mediaProgress
|
||||||
|
const libraryItemId = this.$store.state.streamLibraryItem?.id
|
||||||
|
if (!libraryItemId) return
|
||||||
|
const episodeId = this.$store.state.streamEpisodeId
|
||||||
|
const progressPath = episodeId ? `${libraryItemId}/${episodeId}` : libraryItemId
|
||||||
|
this.$axios.$patch(`/api/me/progress/${progressPath}`, { playbackRate }).catch((err) => {
|
||||||
|
console.error('Failed to save playback rate to progress', err)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
setPlaybackRate(playbackRate) {
|
setPlaybackRate(playbackRate) {
|
||||||
this.$emit('setPlaybackRate', playbackRate)
|
this.$emit('setPlaybackRate', playbackRate)
|
||||||
|
|
@ -321,15 +336,27 @@ export default {
|
||||||
showPlayerSettings() {
|
showPlayerSettings() {
|
||||||
this.showPlayerSettingsModal = !this.showPlayerSettingsModal
|
this.showPlayerSettingsModal = !this.showPlayerSettingsModal
|
||||||
},
|
},
|
||||||
|
initPlaybackRate() {
|
||||||
|
const libraryItemId = this.$store.state.streamLibraryItem?.id
|
||||||
|
const episodeId = this.$store.state.streamEpisodeId
|
||||||
|
const mediaProgress = this.$store.getters['user/getUserMediaProgress'](libraryItemId, episodeId)
|
||||||
|
this.playbackRate = mediaProgress?.playbackRate || this.$store.getters['user/getUserSetting']('playbackRate') || 1
|
||||||
|
this.setPlaybackRate(this.playbackRate)
|
||||||
|
},
|
||||||
init() {
|
init() {
|
||||||
this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1
|
this.initPlaybackRate()
|
||||||
|
|
||||||
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
|
||||||
this.setPlaybackRate(this.playbackRate)
|
|
||||||
},
|
},
|
||||||
settingsUpdated(settings) {
|
settingsUpdated(settings) {
|
||||||
if (settings.playbackRate && this.playbackRate !== settings.playbackRate) {
|
if (settings.playbackRate && this.playbackRate !== settings.playbackRate) {
|
||||||
this.setPlaybackRate(settings.playbackRate)
|
// Don't let global setting override a per-book rate
|
||||||
|
const libraryItemId = this.$store.state.streamLibraryItem?.id
|
||||||
|
const episodeId = this.$store.state.streamEpisodeId
|
||||||
|
const mediaProgress = this.$store.getters['user/getUserMediaProgress'](libraryItemId, episodeId)
|
||||||
|
if (!mediaProgress?.playbackRate) {
|
||||||
|
this.setPlaybackRate(settings.playbackRate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closePlayer() {
|
closePlayer() {
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ class MediaProgress extends Model {
|
||||||
hideFromContinueListening: !!this.hideFromContinueListening,
|
hideFromContinueListening: !!this.hideFromContinueListening,
|
||||||
ebookLocation: this.ebookLocation,
|
ebookLocation: this.ebookLocation,
|
||||||
ebookProgress: this.ebookProgress,
|
ebookProgress: this.ebookProgress,
|
||||||
|
playbackRate: this.extraData?.playbackRate || null,
|
||||||
lastUpdate: this.updatedAt.valueOf(),
|
lastUpdate: this.updatedAt.valueOf(),
|
||||||
startedAt: this.createdAt.valueOf(),
|
startedAt: this.createdAt.valueOf(),
|
||||||
finishedAt: this.finishedAt?.valueOf() || null
|
finishedAt: this.finishedAt?.valueOf() || null
|
||||||
|
|
@ -209,6 +210,11 @@ class MediaProgress extends Model {
|
||||||
this.changed('extraData', true)
|
this.changed('extraData', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (progressPayload.playbackRate !== undefined) {
|
||||||
|
this.extraData.playbackRate = progressPayload.playbackRate
|
||||||
|
this.changed('extraData', true)
|
||||||
|
}
|
||||||
|
|
||||||
this.set(progressPayload)
|
this.set(progressPayload)
|
||||||
|
|
||||||
// Reset hideFromContinueListening if the progress has changed
|
// Reset hideFromContinueListening if the progress has changed
|
||||||
|
|
|
||||||
102
test/server/models/MediaProgress.test.js
Normal file
102
test/server/models/MediaProgress.test.js
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
const { expect } = require('chai')
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
const { Sequelize } = require('sequelize')
|
||||||
|
const Database = require('../../../server/Database')
|
||||||
|
const Logger = require('../../../server/Logger')
|
||||||
|
|
||||||
|
describe('MediaProgress', () => {
|
||||||
|
let mediaProgress
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
global.ServerSettings = {}
|
||||||
|
Database.sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
||||||
|
Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '')
|
||||||
|
await Database.buildModels()
|
||||||
|
|
||||||
|
const user = await Database.userModel.create({ username: 'testuser', type: 'root' })
|
||||||
|
const library = await Database.libraryModel.create({ name: 'Test Library', mediaType: 'book' })
|
||||||
|
const libraryFolder = await Database.libraryFolderModel.create({ path: '/test', libraryId: library.id })
|
||||||
|
const book = await Database.bookModel.create({ title: 'Test Book', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] })
|
||||||
|
const libraryItem = await Database.libraryItemModel.create({ libraryFiles: [], mediaId: book.id, mediaType: 'book', libraryId: library.id, libraryFolderId: libraryFolder.id })
|
||||||
|
|
||||||
|
mediaProgress = await Database.mediaProgressModel.create({
|
||||||
|
mediaItemId: book.id,
|
||||||
|
mediaItemType: 'book',
|
||||||
|
userId: user.id,
|
||||||
|
duration: 36000,
|
||||||
|
currentTime: 1234.5,
|
||||||
|
extraData: { libraryItemId: libraryItem.id, progress: 0.034 }
|
||||||
|
})
|
||||||
|
|
||||||
|
sinon.stub(Logger, 'info')
|
||||||
|
sinon.stub(Logger, 'error')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
sinon.restore()
|
||||||
|
await Database.sequelize.sync({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getOldMediaProgress', () => {
|
||||||
|
it('includes playbackRate from extraData when set', () => {
|
||||||
|
mediaProgress.extraData = { ...mediaProgress.extraData, playbackRate: 1.5 }
|
||||||
|
const result = mediaProgress.getOldMediaProgress()
|
||||||
|
expect(result.playbackRate).to.equal(1.5)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when playbackRate not in extraData', () => {
|
||||||
|
const result = mediaProgress.getOldMediaProgress()
|
||||||
|
expect(result.playbackRate).to.be.null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when extraData is null', () => {
|
||||||
|
mediaProgress.extraData = null
|
||||||
|
const result = mediaProgress.getOldMediaProgress()
|
||||||
|
expect(result.playbackRate).to.be.null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves existing extraData fields', () => {
|
||||||
|
mediaProgress.extraData = { libraryItemId: 'li_test', progress: 0.5, playbackRate: 2.0 }
|
||||||
|
const result = mediaProgress.getOldMediaProgress()
|
||||||
|
expect(result.playbackRate).to.equal(2.0)
|
||||||
|
expect(result.progress).to.equal(0.5)
|
||||||
|
expect(result.libraryItemId).to.equal('li_test')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('applyProgressUpdate', () => {
|
||||||
|
it('stores playbackRate in extraData', async () => {
|
||||||
|
await mediaProgress.applyProgressUpdate({ playbackRate: 1.5 })
|
||||||
|
expect(mediaProgress.extraData.playbackRate).to.equal(1.5)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates existing playbackRate', async () => {
|
||||||
|
mediaProgress.extraData = { ...mediaProgress.extraData, playbackRate: 1.5 }
|
||||||
|
await mediaProgress.applyProgressUpdate({ playbackRate: 2.0 })
|
||||||
|
expect(mediaProgress.extraData.playbackRate).to.equal(2.0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not touch playbackRate when not in payload', async () => {
|
||||||
|
mediaProgress.extraData = { ...mediaProgress.extraData, playbackRate: 1.5 }
|
||||||
|
await mediaProgress.applyProgressUpdate({ currentTime: 5000 })
|
||||||
|
expect(mediaProgress.extraData.playbackRate).to.equal(1.5)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves other extraData fields when setting playbackRate', async () => {
|
||||||
|
const originalLibraryItemId = mediaProgress.extraData.libraryItemId
|
||||||
|
const originalProgress = mediaProgress.extraData.progress
|
||||||
|
await mediaProgress.applyProgressUpdate({ playbackRate: 1.75 })
|
||||||
|
expect(mediaProgress.extraData.libraryItemId).to.equal(originalLibraryItemId)
|
||||||
|
expect(mediaProgress.extraData.progress).to.equal(originalProgress)
|
||||||
|
expect(mediaProgress.extraData.playbackRate).to.equal(1.75)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('initializes extraData if null', async () => {
|
||||||
|
mediaProgress.extraData = null
|
||||||
|
await mediaProgress.applyProgressUpdate({ playbackRate: 1.25 })
|
||||||
|
expect(mediaProgress.extraData).to.be.an('object')
|
||||||
|
expect(mediaProgress.extraData.playbackRate).to.equal(1.25)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue