pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add time remaining to pause overlay

Pas f1af25bf 4630d782

+69 -2
+69 -2
src/components/player/overlays/PauseOverlay.tsx
··· 1 1 import { useEffect, useState } from "react"; 2 + import { useTranslation } from "react-i18next"; 2 3 import { useIdle } from "react-use"; 3 4 4 5 import { ··· 12 13 import { playerStatus } from "@/stores/player/slices/source"; 13 14 import { usePlayerStore } from "@/stores/player/store"; 14 15 import { usePreferencesStore } from "@/stores/preferences"; 16 + import { durationExceedsHour, formatSeconds } from "@/utils/formatSeconds"; 17 + import { uses12HourClock } from "@/utils/uses12HourClock"; 15 18 16 19 interface PauseDetails { 17 20 voteAverage: number | null; ··· 23 26 const isPaused = usePlayerStore((s) => s.mediaPlaying.isPaused); 24 27 const status = usePlayerStore((s) => s.status); 25 28 const meta = usePlayerStore((s) => s.meta); 29 + const { time, duration, draggingTime } = usePlayerStore((s) => s.progress); 30 + const { isSeeking } = usePlayerStore((s) => s.interface); 31 + const playbackRate = usePlayerStore((s) => s.mediaPlaying.playbackRate); 26 32 const enablePauseOverlay = usePreferencesStore((s) => s.enablePauseOverlay); 27 33 const enableImageLogos = usePreferencesStore((s) => s.enableImageLogos); 28 34 const { isMobile } = useIsMobile(); 29 35 const { showTargets } = useShouldShowControls(); 36 + const { t } = useTranslation(); 30 37 const [logoUrl, setLogoUrl] = useState<string | null>(null); 31 38 const [details, setDetails] = useState<PauseDetails>({ 32 39 voteAverage: null, ··· 117 124 const overview = 118 125 meta.type === "show" ? meta.episode?.overview : meta.overview; 119 126 127 + const hasHours = durationExceedsHour(duration); 128 + const currentTime = Math.min( 129 + Math.max(isSeeking ? draggingTime : time, 0), 130 + duration, 131 + ); 132 + const secondsRemaining = Math.abs(currentTime - duration); 133 + const secondsRemainingAdjusted = 134 + playbackRate > 0 ? secondsRemaining / playbackRate : secondsRemaining; 135 + 136 + const timeLeft = formatSeconds( 137 + secondsRemaining, 138 + durationExceedsHour(secondsRemaining), 139 + ); 140 + const timeWatched = formatSeconds(currentTime, hasHours); 141 + const timeFinished = new Date(Date.now() + secondsRemainingAdjusted * 1e3); 142 + const durationFormatted = formatSeconds(duration, hasHours); 143 + 144 + const localizationKey = "remaining"; 145 + 120 146 // Don't render anything if we don't have content, but keep structure for fade if valid 121 147 const hasDetails = details.voteAverage !== null || details.genres.length > 0; 122 148 const hasContent = overview || logoUrl || meta.title || hasDetails; ··· 128 154 shouldShow ? "opacity-100" : "opacity-0" 129 155 }`} 130 156 > 131 - <div className="md:ml-16 max-w-sm lg:max-w-2xl p-8"> 157 + <div className="md:ml-16 max-w-md lg:max-w-2xl p-8"> 132 158 {logoUrl ? ( 133 159 <img 134 160 src={logoUrl} ··· 147 173 </h2> 148 174 )} 149 175 150 - {(details.voteAverage !== null || details.genres.length > 0) && ( 176 + {(details.voteAverage !== null || 177 + details.genres.length > 0 || 178 + duration > 0) && ( 151 179 <div className="mb-3 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-white/80 drop-shadow-md"> 152 180 {details.voteAverage !== null && ( 153 181 <span> ··· 161 189 <span className="text-white/60">•</span> 162 190 )} 163 191 <span>{details.genres.slice(0, 4).join(", ")}</span> 192 + </> 193 + )} 194 + {duration > 0 && ( 195 + <> 196 + {(details.voteAverage !== null || 197 + details.genres.length > 0) && ( 198 + <span className="text-white/60">•</span> 199 + )} 200 + <span> 201 + {(() => { 202 + const text = t(`player.time.${localizationKey}`, { 203 + timeFinished, 204 + timeWatched, 205 + timeLeft, 206 + duration: durationFormatted, 207 + formatParams: { 208 + timeFinished: { 209 + hour: "numeric", 210 + minute: "numeric", 211 + hour12: uses12HourClock(), 212 + }, 213 + }, 214 + }); 215 + if ( 216 + localizationKey === "remaining" && 217 + text.includes(" • ") 218 + ) { 219 + const [left, right] = text.split(" • "); 220 + return ( 221 + <> 222 + {left} 223 + <span className="text-white/60 mx-1">•</span> 224 + {right} 225 + </> 226 + ); 227 + } 228 + return text; 229 + })()} 230 + </span> 164 231 </> 165 232 )} 166 233 </div>