66 :disabled =" !canSeek"
77 style =" width : 100% "
88 :min =" 0"
9- :max =" store.curQueueItem?.duration"
9+ :max ="
10+ store.curQueueItem?.duration ||
11+ store.activePlayer?.current_media?.duration
12+ "
1013 hide-details
1114 :track-size =" 4"
1215 :thumb-size =" isThumbHidden ? 0 : 10"
@@ -63,7 +66,8 @@ import { MediaType } from "@/plugins/api/interfaces";
6366import { store } from " @/plugins/store" ;
6467import { useActiveSource } from " @/composables/activeSource" ;
6568import { formatDuration } from " @/helpers/utils" ;
66- import { ref , computed , watch , toRef } from " vue" ;
69+ import { ref , computed , watch , toRef , onMounted , onUnmounted } from " vue" ;
70+ import computeElapsedTime from " @/helpers/elapsed" ;
6771
6872// properties
6973export interface Props {
@@ -84,52 +88,173 @@ const isThumbHidden = ref(true);
8488const isDragging = ref (false );
8589const curTimeValue = ref (0 );
8690const tempTime = ref (0 );
91+ // ticking ref to force recompute of elapsed time (Date.now() is non-reactive)
92+ const nowTick = ref (0 );
93+ let tickTimer: ReturnType <typeof setInterval > | null = null ;
94+
95+ const startTick = (interval = 500 ) => {
96+ if (! tickTimer )
97+ tickTimer = setInterval (() => (nowTick .value = Date .now ()), interval );
98+ };
99+
100+ const stopTick = () => {
101+ if (tickTimer ) {
102+ clearInterval (tickTimer );
103+ tickTimer = null ;
104+ }
105+ };
106+
107+ onUnmounted (() => {
108+ stopTick ();
109+ });
87110
88111// computed properties
89112const canSeek = computed (() => {
90113 // Check if active source allows seeking
91- // commented out to fix issue first with actually retrieving the elapsed time of the source
92- // if (activeSource.value) {
93- // return activeSource.value.can_seek;
94- // }
114+ if (activeSource .value ) {
115+ // When an active external source is present, only allow seeking if the
116+ // source reports that it supports seeking (can_seek) AND the current
117+ // media (or queue item) has a known duration. We also keep checks for
118+ // powered state and radio streams.
119+ if (store .activePlayer ?.powered === false ) return false ;
120+
121+ // If queue is active prefer queue duration
122+ const queueHasDuration = !! store .curQueueItem ?.duration ;
123+ const currentMediaDuration = store .activePlayer ?.current_media ?.duration ;
124+ const currentMediaHasDuration = !! currentMediaDuration ;
125+
126+ // Disallow seeking for radio streams
127+ const isRadio =
128+ store .curQueueItem ?.media_item ?.media_type == MediaType .RADIO ||
129+ store .activePlayer ?.current_media ?.media_type == MediaType .RADIO ;
130+ if (isRadio ) return false ;
131+
132+ // If the active source reports can_seek, allow seeking when there is a
133+ // duration available (either queue item or current_media).
134+ if (
135+ activeSource .value .can_seek &&
136+ (queueHasDuration || currentMediaHasDuration )
137+ )
138+ return true ;
139+
140+ return false ;
141+ }
95142
96143 if (store .curQueueItem ?.media_item ?.media_type == MediaType .RADIO )
97144 return false ;
98145 if (store .activePlayer ?.powered == false ) return false ;
99146 if (! store .curQueueItem ) return false ;
100147 if (! store .curQueueItem .media_item ) return false ;
148+ // Duration must be present and truthy (non-zero) for seeking when using the
149+ // local queue. Elapsed_time may be 0, but duration of 0 isn't seekable.
101150 if (! store .curQueueItem .duration ) return false ;
102151
103152 // Default to true if no active source (queue control)
104153 return true ;
105154});
106155
107156const playerCurTimeStr = computed (() => {
108- if (! store .curQueueItem ) return " 0:00" ;
109- if (showRemainingTime .value ) {
110- return ` -${formatDuration (
111- store .curQueueItem .duration - curQueueItemTime .value ,
112- )} ` ;
113- } else {
157+ // If there's an active queue item use its duration arithmetic
158+ if (store .curQueueItem ) {
159+ if (showRemainingTime .value ) {
160+ return ` -${formatDuration (
161+ store .curQueueItem .duration - curQueueItemTime .value ,
162+ )} ` ;
163+ }
114164 return ` ${formatDuration (curQueueItemTime .value )} ` ;
115165 }
166+
167+ // No queue item: prefer current_media elapsed when available
168+ if (
169+ store .activePlayer ?.current_media ?.elapsed_time != null ||
170+ store .activePlayer ?.elapsed_time != null
171+ ) {
172+ const val = curQueueItemTime .value || 0 ;
173+ if (showRemainingTime .value && store .activePlayer ?.current_media ?.duration )
174+ return ` -${formatDuration (
175+ store .activePlayer .current_media .duration - val ,
176+ )} ` ;
177+ return ` ${formatDuration (val )} ` ;
178+ }
179+
180+ return " 0:00" ;
116181});
117182
118183const playerTotalTimeStr = computed (() => {
119- if (! store .curQueueItem ) return " " ;
120- if (! store .curQueueItem .duration ) return " " ;
121- if (store .curQueueItem .media_item ?.media_type == MediaType .RADIO ) return " " ;
122- const totalSecs = store .curQueueItem .duration ;
123- return formatDuration (totalSecs );
184+ // Prefer queue item duration, fall back to current_media duration for external sources
185+ const duration =
186+ store .curQueueItem ?.duration || store .activePlayer ?.current_media ?.duration ;
187+ if (! duration ) return " " ;
188+ // If radio/streaming with unknown duration, don't show
189+ const isRadio =
190+ store .curQueueItem ?.media_item ?.media_type == MediaType .RADIO ||
191+ store .activePlayer ?.current_media ?.media_type == MediaType .RADIO ;
192+ if (isRadio ) return " " ;
193+ return formatDuration (duration );
124194});
125195
126196const curQueueItemTime = computed (() => {
197+ // include nowTick.value so this computed re-evaluates periodically while mounted
198+ // and updates UI for fallback player-level current_media that relies on Date.now()
199+ void nowTick .value ;
200+
201+ // Adaptive tick: only run the timer when we have a playing source that relies on time progression
202+ const isPlaying = store .activePlayer ?.playback_state === " playing" ;
203+ const usingQueue = !! (
204+ store .activePlayerQueue && store .activePlayerQueue .active
205+ );
206+ const hasCurrentMedia =
207+ store .activePlayer ?.current_media ?.elapsed_time != null ;
208+
209+ // Start ticking when playing and either using queue or external current_media
210+ if (isPlaying && (usingQueue || hasCurrentMedia )) startTick ();
211+ else stopTick ();
127212 if (isDragging .value ) {
128213 // eslint-disable-next-line vue/no-side-effects-in-computed-properties
129214 tempTime .value = curTimeValue .value ;
130215 return curTimeValue .value ;
131216 }
132- if (store .activePlayerQueue ) return store .activePlayerQueue .elapsed_time ;
217+
218+ // Prefer queue-level elapsed_time if available
219+ const queue = store .activePlayerQueue ;
220+ if (queue ?.elapsed_time != null && queue ?.elapsed_time_last_updated != null ) {
221+ const computed = computeElapsedTime (
222+ queue .elapsed_time ,
223+ queue .elapsed_time_last_updated ,
224+ store .activePlayer ?.playback_state ,
225+ );
226+ return computed ?? 0 ;
227+ }
228+
229+ // Fallback to player-level elapsed_time. This is used for external/3rd-party
230+ // sources currently playing on the player (not for Music Assistant queue
231+ // playback). Use the player-level fields when no activePlayerQueue is set.
232+ // Prefer current_media timing when available (external source playing on the player)
233+ if (
234+ store .activePlayer ?.current_media ?.elapsed_time != null &&
235+ store .activePlayer ?.current_media ?.elapsed_time_last_updated != null
236+ ) {
237+ const computed = computeElapsedTime (
238+ store .activePlayer .current_media .elapsed_time ,
239+ store .activePlayer .current_media .elapsed_time_last_updated ,
240+ store .activePlayer ?.playback_state ,
241+ );
242+ return computed ?? 0 ;
243+ }
244+
245+ // Fall back to player-level elapsed_time (legacy / provider-level value)
246+ if (
247+ store .activePlayer ?.elapsed_time != null &&
248+ store .activePlayer ?.elapsed_time_last_updated != null
249+ ) {
250+ const computed = computeElapsedTime (
251+ store .activePlayer .elapsed_time ,
252+ store .activePlayer .elapsed_time_last_updated ,
253+ store .activePlayer ?.playback_state ,
254+ );
255+ return computed ?? 0 ;
256+ }
257+
133258 return 0 ;
134259});
135260
0 commit comments