DUNGEONS-34: implement Webka local streaming app
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
88
frontend/pages/watch.vue
Normal file
88
frontend/pages/watch.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
LogOut,
|
||||
Radio,
|
||||
Wifi,
|
||||
RotateCw,
|
||||
} from "lucide-vue-next";
|
||||
const { t } = useI18n();
|
||||
useHead({ title: computed(() => `${t("nav.watch")} · Webka`) });
|
||||
const session = useSessionStore();
|
||||
const { remote, state, error, resolution, start, stop } = useStream("viewer");
|
||||
const player = ref<HTMLElement>(),
|
||||
full = ref(false),
|
||||
actionError = ref("");
|
||||
onMounted(() => {
|
||||
void start();
|
||||
document.addEventListener("fullscreenchange", changed);
|
||||
});
|
||||
onBeforeUnmount(() =>
|
||||
document.removeEventListener("fullscreenchange", changed),
|
||||
);
|
||||
function changed() {
|
||||
full.value = !!document.fullscreenElement;
|
||||
}
|
||||
async function fullscreen() {
|
||||
try {
|
||||
if (document.fullscreenElement) await document.exitFullscreen();
|
||||
else await player.value?.requestFullscreen();
|
||||
} catch {
|
||||
actionError.value = t("errors.fullscreen");
|
||||
}
|
||||
}
|
||||
async function disconnect() {
|
||||
stop();
|
||||
await session.logout();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div ref="player" class="watch-page">
|
||||
<StreamVideo v-if="remote" :stream="remote" />
|
||||
<div v-else class="watch-empty">
|
||||
<Radio :size="44" :stroke-width="1.2" />
|
||||
<div class="eyebrow">{{ $t(`stream.${state}`) }}</div>
|
||||
<h1>
|
||||
{{
|
||||
$t(
|
||||
state === "waiting"
|
||||
? "watch.waitingTitle"
|
||||
: "watch.connectingTitle",
|
||||
)
|
||||
}}
|
||||
</h1>
|
||||
<p>
|
||||
{{
|
||||
$t(state === "waiting" ? "watch.waitingHint" : "watch.connectingHint")
|
||||
}}
|
||||
</p>
|
||||
<GlassButton v-if="state === 'offline'" @click="start"
|
||||
><RotateCw :size="18" />{{ $t("watch.reconnect") }}</GlassButton
|
||||
>
|
||||
</div>
|
||||
<p v-if="error || actionError" class="error watch-error" role="alert">
|
||||
{{ error || actionError }}
|
||||
</p>
|
||||
<GlassCard class="watch-controls"
|
||||
><span class="watch-state" role="status"
|
||||
><span class="status-dot" :class="{ live: state === 'live' }" />{{
|
||||
$t(`stream.${state}`)
|
||||
}}</span
|
||||
><span class="watch-quality"
|
||||
><Wifi :size="17" />{{ resolution || $t("quality.auto") }}</span
|
||||
><GlassButton
|
||||
class="icon-button"
|
||||
:aria-label="$t('watch.fullscreen')"
|
||||
@click="fullscreen"
|
||||
><Minimize2 v-if="full" :size="20" /><Maximize2
|
||||
v-else
|
||||
:size="20" /></GlassButton
|
||||
><GlassButton @click="disconnect"
|
||||
><LogOut :size="18" /><span>{{
|
||||
$t("watch.disconnect")
|
||||
}}</span></GlassButton
|
||||
><LanguageSwitcher v-if="full"
|
||||
/></GlassCard>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user