mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-02-28 21:19:42 +00:00
Merge cdd9800dff into 1d0b7e383a
This commit is contained in:
commit
b0ce5d41ea
2 changed files with 227 additions and 3 deletions
|
|
@ -148,6 +148,10 @@ export default {
|
|||
currentChapter() {
|
||||
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
|
||||
},
|
||||
useChapterTrack() {
|
||||
const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false
|
||||
return this.chapters.length ? _useChapterTrack : false
|
||||
},
|
||||
title() {
|
||||
if (this.playerHandler.displayTitle) return this.playerHandler.displayTitle
|
||||
return this.mediaMetadata.title || 'No Title'
|
||||
|
|
@ -291,6 +295,8 @@ export default {
|
|||
setPlaybackRate(playbackRate) {
|
||||
this.currentPlaybackRate = playbackRate
|
||||
this.playerHandler.setPlaybackRate(playbackRate)
|
||||
// Update position state with new playback rate
|
||||
this.updateMediaSessionPositionState()
|
||||
},
|
||||
seek(time) {
|
||||
this.playerHandler.seek(time)
|
||||
|
|
@ -300,6 +306,7 @@ export default {
|
|||
this.playerHandler.seek(time, false)
|
||||
},
|
||||
setCurrentTime(time) {
|
||||
const previousChapterId = this.currentChapter?.id
|
||||
this.currentTime = time
|
||||
if (this.$refs.audioPlayer) {
|
||||
this.$refs.audioPlayer.setCurrentTime(time)
|
||||
|
|
@ -308,6 +315,14 @@ export default {
|
|||
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
|
||||
this.checkChapterEnd()
|
||||
}
|
||||
|
||||
// Update MediaSession position state (chapter-relative when useChapterTrack enabled)
|
||||
this.updateMediaSessionPositionState()
|
||||
|
||||
// If chapter changed and useChapterTrack enabled, update MediaSession metadata
|
||||
if (this.useChapterTrack && this.currentChapter?.id !== previousChapterId && this.currentChapter) {
|
||||
this.updateMediaSessionForChapter()
|
||||
}
|
||||
},
|
||||
setDuration(duration) {
|
||||
this.totalDuration = duration
|
||||
|
|
@ -355,7 +370,20 @@ export default {
|
|||
mediaSessionSeekTo(e) {
|
||||
console.log('Media session seek to', e)
|
||||
if (e.seekTime !== null && !isNaN(e.seekTime)) {
|
||||
this.playerHandler.seek(e.seekTime)
|
||||
// When "Use chapter track" is enabled and chapters exist, seekTime is
|
||||
// relative to current chapter start. Map it back to absolute position.
|
||||
if (this.useChapterTrack && this.currentChapter) {
|
||||
const chapterStart = this.currentChapter.start
|
||||
const chapterEnd = this.currentChapter.end
|
||||
const chapterDuration = chapterEnd - chapterStart
|
||||
// Clamp seekTime to chapter bounds to prevent seeking outside chapter
|
||||
const clampedSeekTime = Math.max(0, Math.min(e.seekTime, chapterDuration))
|
||||
const absoluteTime = chapterStart + clampedSeekTime
|
||||
this.playerHandler.seek(absoluteTime)
|
||||
} else {
|
||||
// "Use chapter track" disabled or no chapters - use full-file seek
|
||||
this.playerHandler.seek(e.seekTime)
|
||||
}
|
||||
}
|
||||
},
|
||||
mediaSessionPreviousTrack() {
|
||||
|
|
@ -373,6 +401,91 @@ export default {
|
|||
navigator.mediaSession.playbackState = this.isPlaying ? 'playing' : 'paused'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update MediaSession metadata when chapter changes (only if useChapterTrack is enabled).
|
||||
* Updates the title to show chapter info and resets position state.
|
||||
*/
|
||||
updateMediaSessionForChapter() {
|
||||
if (!('mediaSession' in navigator) || !this.useChapterTrack || !this.currentChapter) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update metadata with chapter title
|
||||
const baseTitle = this.title
|
||||
const chapterTitle = this.currentChapter.title
|
||||
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: chapterTitle || baseTitle,
|
||||
artist: this.playerHandler.displayAuthor || this.mediaMetadata.authorName || 'Unknown',
|
||||
album: baseTitle,
|
||||
artwork: [
|
||||
{
|
||||
src: this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png', true)
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Immediately update position state for new chapter
|
||||
this.updateMediaSessionPositionState()
|
||||
},
|
||||
/**
|
||||
* Update MediaSession position state.
|
||||
* When "Use chapter track" is enabled and a chapter is active, reports
|
||||
* duration/position relative to chapter bounds so the OS scrubber spans
|
||||
* only the current chapter. Otherwise uses full-file duration/position.
|
||||
*/
|
||||
updateMediaSessionPositionState() {
|
||||
if (!('mediaSession' in navigator) || !navigator.mediaSession.setPositionState) {
|
||||
return
|
||||
}
|
||||
|
||||
const playbackRate = this.currentPlaybackRate || 1
|
||||
|
||||
if (this.useChapterTrack && this.currentChapter) {
|
||||
// "Use chapter track" enabled - report chapter-relative values
|
||||
const chapterStart = this.currentChapter.start
|
||||
const chapterEnd = this.currentChapter.end
|
||||
const chapterDuration = chapterEnd - chapterStart
|
||||
|
||||
// Calculate position relative to chapter start
|
||||
// Clamp to valid range to handle slight timing drift
|
||||
let chapterPosition = this.currentTime - chapterStart
|
||||
chapterPosition = Math.max(0, Math.min(chapterPosition, chapterDuration))
|
||||
|
||||
// Validate values to prevent NaN or invalid states
|
||||
if (isNaN(chapterDuration) || chapterDuration <= 0 || isNaN(chapterPosition)) {
|
||||
console.warn('Invalid chapter position state values, skipping update')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
navigator.mediaSession.setPositionState({
|
||||
duration: chapterDuration,
|
||||
position: chapterPosition,
|
||||
playbackRate: playbackRate
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error setting media session position state:', e)
|
||||
}
|
||||
} else if (this.totalDuration > 0) {
|
||||
// "Use chapter track" disabled or no chapters - use full-file values
|
||||
const position = Math.max(0, Math.min(this.currentTime, this.totalDuration))
|
||||
|
||||
if (isNaN(this.totalDuration) || isNaN(position)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
navigator.mediaSession.setPositionState({
|
||||
duration: this.totalDuration,
|
||||
position: position,
|
||||
playbackRate: playbackRate
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error setting media session position state:', e)
|
||||
}
|
||||
}
|
||||
},
|
||||
setMediaSession() {
|
||||
if (!this.streamLibraryItem) {
|
||||
console.error('setMediaSession: No library item set')
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ export default {
|
|||
listeningTimeSinceSync: 0,
|
||||
coverRgb: null,
|
||||
coverBgIsLight: false,
|
||||
currentTime: 0
|
||||
currentTime: 0,
|
||||
currentPlaybackRate: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -88,6 +89,10 @@ export default {
|
|||
currentChapter() {
|
||||
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
|
||||
},
|
||||
useChapterTrack() {
|
||||
const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false
|
||||
return this.chapters.length ? _useChapterTrack : false
|
||||
},
|
||||
coverAspectRatio() {
|
||||
const coverAspectRatio = this.playbackSession.coverAspectRatio
|
||||
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
||||
|
|
@ -133,7 +138,20 @@ export default {
|
|||
mediaSessionSeekTo(e) {
|
||||
console.log('Media session seek to', e)
|
||||
if (e.seekTime !== null && !isNaN(e.seekTime)) {
|
||||
this.seek(e.seekTime)
|
||||
// When "Use chapter track" is enabled and chapters exist, seekTime is
|
||||
// relative to current chapter start. Map it back to absolute position.
|
||||
if (this.useChapterTrack && this.currentChapter) {
|
||||
const chapterStart = this.currentChapter.start
|
||||
const chapterEnd = this.currentChapter.end
|
||||
const chapterDuration = chapterEnd - chapterStart
|
||||
// Clamp seekTime to chapter bounds to prevent seeking outside chapter
|
||||
const clampedSeekTime = Math.max(0, Math.min(e.seekTime, chapterDuration))
|
||||
const absoluteTime = chapterStart + clampedSeekTime
|
||||
this.seek(absoluteTime)
|
||||
} else {
|
||||
// "Use chapter track" disabled or no chapters - use full-file seek
|
||||
this.seek(e.seekTime)
|
||||
}
|
||||
}
|
||||
},
|
||||
mediaSessionPreviousTrack() {
|
||||
|
|
@ -151,6 +169,86 @@ export default {
|
|||
navigator.mediaSession.playbackState = this.isPlaying ? 'playing' : 'paused'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update MediaSession metadata when chapter changes (only if useChapterTrack is enabled).
|
||||
* Updates the title to show chapter info and resets position state.
|
||||
*/
|
||||
updateMediaSessionForChapter() {
|
||||
if (!('mediaSession' in navigator) || !this.useChapterTrack || !this.currentChapter) {
|
||||
return
|
||||
}
|
||||
|
||||
const baseTitle = this.mediaItemShare.playbackSession.displayTitle || 'No title'
|
||||
const chapterTitle = this.currentChapter.title
|
||||
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: chapterTitle || baseTitle,
|
||||
artist: this.mediaItemShare.playbackSession.displayAuthor || 'Unknown',
|
||||
album: baseTitle,
|
||||
artwork: [
|
||||
{
|
||||
src: this.coverUrl
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
this.updateMediaSessionPositionState()
|
||||
},
|
||||
/**
|
||||
* Update MediaSession position state.
|
||||
* When "Use chapter track" is enabled and a chapter is active, reports
|
||||
* duration/position relative to chapter bounds so the OS scrubber spans
|
||||
* only the current chapter. Otherwise uses full-file duration/position.
|
||||
*/
|
||||
updateMediaSessionPositionState() {
|
||||
if (!('mediaSession' in navigator) || !navigator.mediaSession.setPositionState) {
|
||||
return
|
||||
}
|
||||
|
||||
const playbackRate = this.currentPlaybackRate || 1
|
||||
|
||||
if (this.useChapterTrack && this.currentChapter) {
|
||||
// "Use chapter track" enabled - report chapter-relative values
|
||||
const chapterStart = this.currentChapter.start
|
||||
const chapterEnd = this.currentChapter.end
|
||||
const chapterDuration = chapterEnd - chapterStart
|
||||
|
||||
let chapterPosition = this.currentTime - chapterStart
|
||||
chapterPosition = Math.max(0, Math.min(chapterPosition, chapterDuration))
|
||||
|
||||
if (isNaN(chapterDuration) || chapterDuration <= 0 || isNaN(chapterPosition)) {
|
||||
console.warn('Invalid chapter position state values, skipping update')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
navigator.mediaSession.setPositionState({
|
||||
duration: chapterDuration,
|
||||
position: chapterPosition,
|
||||
playbackRate: playbackRate
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error setting media session position state:', e)
|
||||
}
|
||||
} else if (this.totalDuration > 0) {
|
||||
// "Use chapter track" disabled or no chapters - use full-file values
|
||||
const position = Math.max(0, Math.min(this.currentTime, this.totalDuration))
|
||||
|
||||
if (isNaN(this.totalDuration) || isNaN(position)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
navigator.mediaSession.setPositionState({
|
||||
duration: this.totalDuration,
|
||||
position: position,
|
||||
playbackRate: playbackRate
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error setting media session position state:', e)
|
||||
}
|
||||
}
|
||||
},
|
||||
setMediaSession() {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API
|
||||
if ('mediaSession' in navigator) {
|
||||
|
|
@ -237,7 +335,10 @@ export default {
|
|||
},
|
||||
setPlaybackRate(playbackRate) {
|
||||
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||
this.currentPlaybackRate = playbackRate
|
||||
this.localAudioPlayer.setPlaybackRate(playbackRate)
|
||||
// Update position state with new playback rate
|
||||
this.updateMediaSessionPositionState()
|
||||
},
|
||||
seek(time) {
|
||||
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||
|
|
@ -248,9 +349,19 @@ export default {
|
|||
setCurrentTime(time) {
|
||||
if (!this.$refs.audioPlayer) return
|
||||
|
||||
const previousChapterId = this.currentChapter?.id
|
||||
|
||||
// Update UI
|
||||
this.$refs.audioPlayer.setCurrentTime(time)
|
||||
this.currentTime = time
|
||||
|
||||
// Update MediaSession position state (chapter-relative when useChapterTrack enabled)
|
||||
this.updateMediaSessionPositionState()
|
||||
|
||||
// If chapter changed and useChapterTrack enabled, update MediaSession metadata
|
||||
if (this.useChapterTrack && this.currentChapter?.id !== previousChapterId && this.currentChapter) {
|
||||
this.updateMediaSessionForChapter()
|
||||
}
|
||||
},
|
||||
setDuration() {
|
||||
if (!this.localAudioPlayer) return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue