diff --git a/src/components/DisplayPage.vue b/src/components/DisplayPage.vue index 350df4e..4d66b75 100644 --- a/src/components/DisplayPage.vue +++ b/src/components/DisplayPage.vue @@ -113,10 +113,14 @@ - +
@@ -129,16 +133,38 @@ export default { mixins: [createWsMixin('display')], data() { return { - spotList: [], // filename dei video spot da riprodurre - spotIndex: 0, // video corrente nella sequenza + mostraPlaceholder: false, // video terminato prima dei 30s: mostra l'immagine "TIME OUT" + spotVideoDuration: null, // durata reale del video in riproduzione, nota dopo il loadedmetadata + nowTick: Date.now(), + timeoutTicker: null, } }, mounted() { if (this.isMobile()) { try { document.documentElement.requestFullscreen() } catch (e) {} } + if (this.state.timeout) this.avviaTimeout() + }, + beforeUnmount() { + this.fermaTimeoutTicker() }, computed: { + timeoutRemaining() { + if (!this.state.timeout || !this.state.timeoutStartedAt) return 30 + const trascorsi = (this.nowTick - this.state.timeoutStartedAt) / 1000 + return Math.max(0, Math.ceil(30 - trascorsi)) + }, + timeoutCountdown() { + const r = this.timeoutRemaining + const m = String(Math.floor(r / 60)).padStart(2, '0') + const s = String(r % 60).padStart(2, '0') + return `${m}:${s}` + }, + timeoutFading() { + // Video più lungo di 30s: dissolvenza negli ultimi istanti prima dello stop del server. + if (!this.spotVideoDuration || this.spotVideoDuration <= 30) return false + return this.timeoutRemaining <= 1 + }, stricciaStrip() { const currentSet = this.state.sp.striscia.at(-1) if (!currentSet) return { home: [], guest: [] } @@ -168,32 +194,41 @@ export default { }, }, methods: { + avviaTimeoutTicker() { + this.fermaTimeoutTicker() + this.nowTick = Date.now() + this.timeoutTicker = setInterval(() => { this.nowTick = Date.now() }, 200) + }, + fermaTimeoutTicker() { + if (this.timeoutTicker) { clearInterval(this.timeoutTicker); this.timeoutTicker = null } + }, async avviaTimeout() { - this.spotIndex = 0 - try { - const res = await fetch('/spot/list') - this.spotList = await res.json() - } catch { - this.spotList = [] - } - if (!this.spotList.length) return + this.mostraPlaceholder = !this.state.timeoutVideo + this.spotVideoDuration = null + this.avviaTimeoutTicker() + if (!this.state.timeoutVideo) return await this.$nextTick() this.caricaSpotCorrente() }, fermaTimeout() { + this.fermaTimeoutTicker() const video = this.$refs.spotVideo if (video) { video.pause(); video.removeAttribute('src'); video.load() } - this.spotList = [] + this.mostraPlaceholder = false + this.spotVideoDuration = null }, caricaSpotCorrente() { const video = this.$refs.spotVideo - if (!video || !this.spotList.length) return - video.src = '/spot/' + encodeURIComponent(this.spotList[this.spotIndex]) - // Avanza al prossimo video alla fine (loop: con un solo video torna su sé stesso) - video.onended = () => { - this.spotIndex = (this.spotIndex + 1) % this.spotList.length - this.caricaSpotCorrente() + if (!video || !this.state.timeoutVideo) return + video.src = '/spot/' + encodeURIComponent(this.state.timeoutVideo) + video.onloadedmetadata = () => { + this.spotVideoDuration = video.duration + // Riconnessione a metà time out: riallinea il video al punto corretto. + const trascorsi = this.state.timeoutStartedAt ? (Date.now() - this.state.timeoutStartedAt) / 1000 : 0 + if (trascorsi > 0.5 && trascorsi < video.duration) video.currentTime = trascorsi } + // Video più corto dei 30s: a fine riproduzione mostra l'immagine "TIME OUT" fino allo stop del server. + video.onended = () => { this.mostraPlaceholder = true } this.playSpot() }, playSpot() { @@ -320,14 +355,37 @@ export default { width: 100%; height: 100%; object-fit: contain; + transition: opacity 0.8s ease-out; +} + +.timeout-video--fading { + opacity: 0; +} + +.timeout-countdown { + position: absolute; + top: 24px; + left: 50%; + transform: translateX(-50%); + color: #fff; + background: rgba(0, 0, 0, 0.5); + font-size: 4vh; + font-weight: 800; + font-variant-numeric: tabular-nums; + letter-spacing: 0.05em; + padding: 6px 18px; + border-radius: 12px; + z-index: 501; } .timeout-placeholder { color: #ff9800; - font-size: 20vh; + font-size: 14vh; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; + text-align: center; + line-height: 1.3; animation: timeout-pulse 1.2s ease-in-out infinite; }