mindboost-rnbo-test-project/pages/index.vue

284 lines
6.9 KiB
Vue

<template>
<div id="step-6">
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<div class="video-wrapper">
<!-- Poster -->
<img
v-if="!videoLoaded"
:src="posterPath"
class="background-poster"
alt="Poster"
>
<!-- Video -->
<transition name="fade">
<video
v-if="showVideo && videoPath"
:key="videoPath"
class="background-video"
:src="videoPath"
autoplay
muted
loop
playsinline
@loadedmetadata="handleFadeIn"
/>
</transition>
<div class="overlay-gradient" />
<div class="page-content">
<PageHeader
:title="currentTitle"
/>
<div v-if="showOverlay" class="overlay">
<button
class="btn btn-primary btn-lg"
style="background-color:#e9c046; border:none; color:black;"
@click="unlockAudio"
>
{{ t('Ready') }}
</button>
</div>
<div class="container-fluid">
<div class="row justify-content-center">
<NavigationBar
v-model:current-index="currentIndex"
:current-soundscape="currentTitle"
:adaptive="adaptive"
@update:soundscape="changeSoundscape"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useAudioStore } from '@/stores/audio'
import { useMicStore } from '@/stores/microphone'
import { useUserStore } from '@/stores/user'
import { getSoundcapeList } from '~/tracks.config.ts'
import PageHeader from '@/components/PageHeader.vue'
import NavigationBar from '@/components/NavigationBar.vue'
// import VideoBackground from '@/components/VideoBackground.vue'
definePageMeta({
middleware: 'auth'
})
const { t } = useI18n()
const router = useRouter()
const audioStore = useAudioStore()
const userStore = useUserStore()
const { $axios } = useNuxtApp()
// States
const isReady = ref(false)
const showOverlay = ref(false)
const soundscapes = getSoundcapeList()
const defaultSoundscape = 'Lagoon'
const currentIndex = userStore.user?.settings?.soundscape
? ref(soundscapes.indexOf(userStore.user.settings.soundscape))
: ref(0)
// Soundscape aus Store oder Fallback
const adaptive = userStore.user?.settings?.adaptive_sound_scape || 'no'
// Computed
const currentTitle = computed(() => userStore.user?.settings?.soundscape || defaultSoundscape)
const videoPath = computed(() => `/video/${currentTitle.value}.mp4`)
const posterPath = computed(() => `/images/posters/${currentTitle.value}.jpg`)
const showVideo = ref(true)
const videoLoaded = ref(false)
// Watcher für AudioContext-Zustand
watch(() => audioStore.audioContext?.state, (newState) => {
showOverlay.value = newState === 'suspended'
})
// Wechsel Video bei Klangänderung
watch(currentIndex, async () => {
videoLoaded.value = false
showVideo.value = false
await nextTick()
showVideo.value = true
})
// Methoden
function changeSoundscape (newSoundscape) {
const map = {
Lagoon: 0,
Tropics: 1,
Forest: 2,
Meadow: 3
}
currentIndex.value = map[newSoundscape.soundscape] ?? 0
unmuteActive()
// Speichern der Einstellungen im Backend
$axios.post('/api/update-setting', newSoundscape).then((response) => {
if (soundscapes.includes(newSoundscape.soundscape)) { userStore.user.settings.soundscape = newSoundscape.soundscape }
if (response.status !== 200) {
// wenn der request fehlgeschlagen hat aus welchen gründen auch immer,
// z.B. keine Internetverbindung, wird ein Interval angeschmissen, dass alle 30 Sekunden probiert die settings zu aktualisieren
// bis es am Ende mal klappt
} else {
// reponse is not 200
}
}).catch((e) => {
useNuxtApp().$toast.error('save: something went wrong while saving...')
})
}
function unmuteActive () {
audioStore.setPlaying(false)
setTimeout(() => {
audioStore.setPlaying(true)
}, 50)
}
async function unlockAudio () {
await audioStore.ensureAudioContextRunning()
}
function handleFadeIn (e) {
e.target.style.opacity = 1
videoLoaded.value = true
}
async function fetchSettings () {
try {
const { data } = await useNuxtApp().$axios.post('/api/fetch-settings')
if (data.success) {
userStore.settings = data.setting
const soundscape = data.setting.soundscape || 'Lagoon'
const index = soundscapes.indexOf(soundscape)
currentIndex.value = index >= 0 ? index : 0 // jetzt erst setzen
// audioStore.changeTrack(soundscape)
}
} catch (error) {
useNuxtApp().$logger.error('Nothing fetched from the backend, please reload and try again.')
}
}
// Lifecycle
onMounted(async () => {
showOverlay.value = true
await useNuxtApp().$audioPrepared
showOverlay.value = false
await fetchSettings()
isReady.value = true
})
onBeforeUnmount(() => {
useAudioStore().stopAudioContextMonitor()
useMicStore().detachMicrophone()
})
</script>
<style>
.video-wrapper {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.background-poster,
.background-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 0;
}
.background-video {
opacity: 0;
transition: opacity 1s ease-in-out;
}
.page-content {
position: relative;
z-index: 1;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 1.5s ease-in-out;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.adaptive{
position: fixed;
bottom: 0;
text-align: center;
width: 100%;
}
.rnboplayer{
position:sticky;
width: 225px;
height: inherit;
display:flex;
float: bottom;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
z-index: 1000;
}
.overlay button {
padding: 10px 20px;
font-size: 18px;
cursor: pointer;
}
.bg-gradient {
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
background: rgb(153,166,188);
background: linear-gradient(0deg, rgba(153,166,188,0) 0%, rgba(153,166,188,0.413624824929972) 55%, rgba(153,166,188,1) 100%);
height: 40vh;
}
}
</style>