mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-05 10:11:38 +00:00
Merge cdd9800dff into c009db9f28
This commit is contained in:
commit
7b3069a67b
2 changed files with 227 additions and 3 deletions
|
|
@ -148,6 +148,10 @@ export default {
|
||||||
currentChapter() {
|
currentChapter() {
|
||||||
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
|
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() {
|
title() {
|
||||||
if (this.playerHandler.displayTitle) return this.playerHandler.displayTitle
|
if (this.playerHandler.displayTitle) return this.playerHandler.displayTitle
|
||||||
return this.mediaMetadata.title || 'No Title'
|
return this.mediaMetadata.title || 'No Title'
|
||||||
|
|
@ -291,6 +295,8 @@ export default {
|
||||||
setPlaybackRate(playbackRate) {
|
setPlaybackRate(playbackRate) {
|
||||||
this.currentPlaybackRate = playbackRate
|
this.currentPlaybackRate = playbackRate
|
||||||
this.playerHandler.setPlaybackRate(playbackRate)
|
this.playerHandler.setPlaybackRate(playbackRate)
|
||||||
|
// Update position state with new playback rate
|
||||||
|
this.updateMediaSessionPositionState()
|
||||||
},
|
},
|
||||||
seek(time) {
|
seek(time) {
|
||||||
this.playerHandler.seek(time)
|
this.playerHandler.seek(time)
|
||||||
|
|
@ -300,6 +306,7 @@ export default {
|
||||||
this.playerHandler.seek(time, false)
|
this.playerHandler.seek(time, false)
|
||||||
},
|
},
|
||||||
setCurrentTime(time) {
|
setCurrentTime(time) {
|
||||||
|
const previousChapterId = this.currentChapter?.id
|
||||||
this.currentTime = time
|
this.currentTime = time
|
||||||
if (this.$refs.audioPlayer) {
|
if (this.$refs.audioPlayer) {
|
||||||
this.$refs.audioPlayer.setCurrentTime(time)
|
this.$refs.audioPlayer.setCurrentTime(time)
|
||||||
|
|
@ -308,6 +315,14 @@ export default {
|
||||||
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
|
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
|
||||||
this.checkChapterEnd()
|
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) {
|
setDuration(duration) {
|
||||||
this.totalDuration = duration
|
this.totalDuration = duration
|
||||||
|
|
@ -355,8 +370,21 @@ export default {
|
||||||
mediaSessionSeekTo(e) {
|
mediaSessionSeekTo(e) {
|
||||||
console.log('Media session seek to', e)
|
console.log('Media session seek to', e)
|
||||||
if (e.seekTime !== null && !isNaN(e.seekTime)) {
|
if (e.seekTime !== null && !isNaN(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)
|
this.playerHandler.seek(e.seekTime)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mediaSessionPreviousTrack() {
|
mediaSessionPreviousTrack() {
|
||||||
if (this.$refs.audioPlayer) {
|
if (this.$refs.audioPlayer) {
|
||||||
|
|
@ -373,6 +401,91 @@ export default {
|
||||||
navigator.mediaSession.playbackState = this.isPlaying ? 'playing' : 'paused'
|
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() {
|
setMediaSession() {
|
||||||
if (!this.streamLibraryItem) {
|
if (!this.streamLibraryItem) {
|
||||||
console.error('setMediaSession: No library item set')
|
console.error('setMediaSession: No library item set')
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,8 @@ export default {
|
||||||
listeningTimeSinceSync: 0,
|
listeningTimeSinceSync: 0,
|
||||||
coverRgb: null,
|
coverRgb: null,
|
||||||
coverBgIsLight: false,
|
coverBgIsLight: false,
|
||||||
currentTime: 0
|
currentTime: 0,
|
||||||
|
currentPlaybackRate: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -88,6 +89,10 @@ export default {
|
||||||
currentChapter() {
|
currentChapter() {
|
||||||
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
|
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() {
|
coverAspectRatio() {
|
||||||
const coverAspectRatio = this.playbackSession.coverAspectRatio
|
const coverAspectRatio = this.playbackSession.coverAspectRatio
|
||||||
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
||||||
|
|
@ -133,8 +138,21 @@ export default {
|
||||||
mediaSessionSeekTo(e) {
|
mediaSessionSeekTo(e) {
|
||||||
console.log('Media session seek to', e)
|
console.log('Media session seek to', e)
|
||||||
if (e.seekTime !== null && !isNaN(e.seekTime)) {
|
if (e.seekTime !== null && !isNaN(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)
|
this.seek(e.seekTime)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mediaSessionPreviousTrack() {
|
mediaSessionPreviousTrack() {
|
||||||
if (this.$refs.audioPlayer) {
|
if (this.$refs.audioPlayer) {
|
||||||
|
|
@ -151,6 +169,86 @@ export default {
|
||||||
navigator.mediaSession.playbackState = this.isPlaying ? 'playing' : 'paused'
|
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() {
|
setMediaSession() {
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API
|
// https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API
|
||||||
if ('mediaSession' in navigator) {
|
if ('mediaSession' in navigator) {
|
||||||
|
|
@ -237,7 +335,10 @@ export default {
|
||||||
},
|
},
|
||||||
setPlaybackRate(playbackRate) {
|
setPlaybackRate(playbackRate) {
|
||||||
if (!this.localAudioPlayer || !this.hasLoaded) return
|
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||||
|
this.currentPlaybackRate = playbackRate
|
||||||
this.localAudioPlayer.setPlaybackRate(playbackRate)
|
this.localAudioPlayer.setPlaybackRate(playbackRate)
|
||||||
|
// Update position state with new playback rate
|
||||||
|
this.updateMediaSessionPositionState()
|
||||||
},
|
},
|
||||||
seek(time) {
|
seek(time) {
|
||||||
if (!this.localAudioPlayer || !this.hasLoaded) return
|
if (!this.localAudioPlayer || !this.hasLoaded) return
|
||||||
|
|
@ -248,9 +349,19 @@ export default {
|
||||||
setCurrentTime(time) {
|
setCurrentTime(time) {
|
||||||
if (!this.$refs.audioPlayer) return
|
if (!this.$refs.audioPlayer) return
|
||||||
|
|
||||||
|
const previousChapterId = this.currentChapter?.id
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
this.$refs.audioPlayer.setCurrentTime(time)
|
this.$refs.audioPlayer.setCurrentTime(time)
|
||||||
this.currentTime = 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() {
|
setDuration() {
|
||||||
if (!this.localAudioPlayer) return
|
if (!this.localAudioPlayer) return
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue