You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
9.1 KiB
287 lines
9.1 KiB
<script>
|
|
import DiscordReplay from "$/client/components/DiscordReplay.svelte";
|
|
import HLSVideo from "$/client/components/HLSVideo.svelte";
|
|
import WTVideo from "$/client/components/WTVideo.svelte";
|
|
import Icon from "$/client/components/Icon.svelte";
|
|
import IconImage from "$/client/components/IconImage.svelte";
|
|
import Layout from '$/client/Layout.svelte';
|
|
import Markdown from "$/client/components/Markdown.svelte";
|
|
import parseISO from "date-fns/parseISO";
|
|
import formatDistanceToNow from "date-fns/formatDistanceToNow";
|
|
import { defer } from "$/client/helpers.js";
|
|
import { onMount } from 'svelte';
|
|
import Toasts from "$/client/components/Toasts.svelte";
|
|
import SnapImage from "$/client/components/SnapImage.svelte";
|
|
import { link } from "svelte-spa-router";
|
|
import { load_past_streams, load_stream, change_stream } from "$/client/livestreams.js";
|
|
import { user } from "$/client/stores.js";
|
|
import { connect_socket, configure_socket } from "$/client/websocket.js";
|
|
|
|
export let params = {};
|
|
let livestream_id = params.livestream_id;
|
|
const autoplay = true;
|
|
let socket;
|
|
let send_toast;
|
|
let stream = {};
|
|
|
|
let load_defer = defer();
|
|
let past_defer = defer();
|
|
|
|
const show_free_archived = (stream) => {
|
|
return stream.state === "archived" && ($user.authenticated || stream.is_free);
|
|
}
|
|
|
|
const load_all = async () => {
|
|
stream = await load_stream(livestream_id, false);
|
|
|
|
load_defer.resolve(stream);
|
|
|
|
if(stream.state === "live") {
|
|
send_toast("Hit play if the video doesn't start.");
|
|
}
|
|
|
|
const past_streams = await load_past_streams();
|
|
past_defer.resolve(past_streams);
|
|
}
|
|
|
|
const change_video_state = async () => {
|
|
const status = await change_stream(livestream_id);
|
|
|
|
if(status === 200) {
|
|
stream = await load_stream(livestream_id, false);
|
|
if(stream.state === "live") {
|
|
send_toast("Stream is live!");
|
|
}
|
|
} else {
|
|
send_toast("Request failed.");
|
|
}
|
|
}
|
|
|
|
$: if(livestream_id !== params.livestream_id) {
|
|
livestream_id = params.livestream_id;
|
|
load_defer = defer();
|
|
past_defer = defer();
|
|
load_all();
|
|
}
|
|
|
|
|
|
onMount(async () => {
|
|
await configure_socket();
|
|
socket = connect_socket();
|
|
|
|
socket.on("/live/update", async (data) => {
|
|
if(data.message) send_toast(data.message);
|
|
load_all();
|
|
});
|
|
|
|
await load_all();
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
|
|
container {
|
|
display: flex;
|
|
flex-direction: row;
|
|
width: 100%;
|
|
}
|
|
|
|
container > left {
|
|
width: 100%;
|
|
max-height: calc(100vh - var(--fixed-header-height));
|
|
overflow-y: auto;
|
|
-ms-overflow-style: none; /* IE and Edge */
|
|
scrollbar-width: none; /* Firefox */
|
|
aspect-ratio: 16/9;
|
|
}
|
|
|
|
/* This combined with the -ms-overflow-style and scrollbar-width is how you remove
|
|
the scrolls on the inner video/notes column.
|
|
*/
|
|
container > left::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
container > left notes {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding-left: 1rem;
|
|
padding-right: 1rem;
|
|
}
|
|
|
|
container > waiting {
|
|
display:flex;
|
|
width: 100%;
|
|
min-height: calc(100vh - var(--fixed-header-height));
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
container > right {
|
|
min-width: 400px;
|
|
max-width: 400px;
|
|
height: calc(100vh - var(--fixed-header-height));
|
|
border-left: 1px solid var(--value7);
|
|
}
|
|
|
|
container > right.video-list {
|
|
padding-left: 0.5rem;
|
|
min-height: calc(100vh - var(--fixed-header-height));
|
|
max-height: calc(100vh - var(--fixed-header-height));
|
|
overflow-y: auto;
|
|
}
|
|
|
|
container > right card {
|
|
border: unset;
|
|
box-shadow: unset;
|
|
}
|
|
|
|
container > right card middle {
|
|
margin-bottom: 0.3rem;
|
|
}
|
|
|
|
span#view-count {
|
|
display: flex;
|
|
align-content: center;
|
|
}
|
|
|
|
@media only screen and (max-width: 800px) {
|
|
right {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<Layout centered={ true } fullwidth={ true } fixed={ true } footer={ false } auth_optional={ true } testid="live-page">
|
|
<container>
|
|
{#await load_defer}
|
|
<br/>
|
|
{:then}
|
|
<left>
|
|
{#if stream.state === "live"}
|
|
<HLSVideo autoplay={ autoplay } poster={ stream.poster } source={ stream.source } />
|
|
{:else if stream.state === "archived" }
|
|
{#if show_free_archived(stream)}
|
|
<WTVideo media={ stream.media } />
|
|
{:else if stream.poster }
|
|
<SnapImage src={ stream.poster } />
|
|
{:else}
|
|
<IconImage height="unset" aspect_ratio="16/9" name="film" />
|
|
{/if}
|
|
{:else if stream.state === "finished"}
|
|
<IconImage height="unset" aspect_ratio="16/9" name="video-off" />
|
|
{:else if stream.state === "pending"}
|
|
<IconImage height="unset" aspect_ratio="16/9" name="clock" />
|
|
{:else}
|
|
<IconImage height="unset" aspect_ratio="16/9" name="video" />
|
|
{/if}
|
|
|
|
<info>
|
|
<tile>
|
|
<left>
|
|
<Icon name="user" size="48" />
|
|
</left>
|
|
<middle>
|
|
<b>Zed A. Shaw</b>
|
|
<span>{ stream.title }</span>
|
|
</middle>
|
|
<right>
|
|
|
|
{#if $user.admin}
|
|
{#if stream.state === "live"}
|
|
<span on:click={ () => change_video_state() }>
|
|
<Icon name="stop-circle" tip_position="bottom-left" tooltip="Stop stream." size="48" color="var(--red)"/>
|
|
</span>
|
|
{:else if stream.state === "pending"}
|
|
<span on:click={ () => change_video_state() }>
|
|
<Icon name="arrow-up-circle" tip_position="bottom-left" tooltip="Announce ready." size="48" color="var(--yellow)"/>
|
|
</span>
|
|
{:else if stream.state === "ready"}
|
|
<span on:click={ () => change_video_state() }>
|
|
<Icon name="cast" size="48" tip_position="bottom-left" tooltip="Go live." color="var(--green)"/>
|
|
</span>
|
|
{:else}
|
|
<a use:link href="/admin/table/livestream/{ stream.id }/">Finished</a>
|
|
{/if}
|
|
{:else}
|
|
<!--
|
|
<span id="view-count">
|
|
<Icon name="user" size="24" /> { stream.viewer_count }
|
|
</span>
|
|
-->
|
|
{/if}
|
|
</right>
|
|
</tile>
|
|
</info>
|
|
|
|
<notes>
|
|
<Markdown content={ stream.description } />
|
|
<br>
|
|
{#if stream.state === "pending"}
|
|
<callout class="info">
|
|
<span data-testid="pending-message">
|
|
This live stream starts at <b>{ stream.starts_on }</b>.
|
|
Come back then, and hit refresh.
|
|
</span>
|
|
</callout>
|
|
{:else if stream.state === "ready"}
|
|
<callout class="info">
|
|
<span>
|
|
The show is about to start. Your page might refresh when it does, but if the video doesn't start on its own hit refresh.
|
|
</span>
|
|
</callout>
|
|
{:else if stream.state === "finished"}
|
|
<callout class="info">
|
|
<span>
|
|
This live stream has ended. It will be posted for later viewing after editing, probably in a few days.
|
|
</span>
|
|
</callout>
|
|
{:else if stream.state === "archived"}
|
|
<callout class="info">
|
|
<span>
|
|
{#if $user.authenticated}
|
|
I hope you enjoyed the stream. If you find a mistake or a problem with the video then please tell me in the chat.
|
|
{:else if show_free_archived(stream) }
|
|
If you enjoyed this free livestream and would like to learn more JavaScript, then <a style="color: var(--value0)" href="/client/#/register/">register today</a> to get even more livestreams and an <b>entire course in JavaScript</b>.
|
|
{:else}
|
|
This live stream is for members only. If you want to learn JavaScript, then <a style="color: var(--value0)" href="/client/#/register/">register today</a> and enjoy all livestreams plus all modules.
|
|
{/if}
|
|
</span>
|
|
</callout>
|
|
{/if}
|
|
</notes>
|
|
</left>
|
|
{#if ["pending", "finished", "archived"].includes(stream.state)}
|
|
<right class="video-list">
|
|
{#await past_defer then streams}
|
|
{#each streams as archived}
|
|
{#if archived.id !== stream.id}
|
|
<card>
|
|
<top>
|
|
<a data-testid="past-link-{ archived.id }" use:link href="/live/{ archived.id }/">
|
|
{#if archived.poster}
|
|
<SnapImage src={ archived.poster } />
|
|
{:else}
|
|
<IconImage name="video" />
|
|
{/if}
|
|
</a>
|
|
</top>
|
|
<middle>
|
|
<b>{ archived.title }</b>
|
|
<div>{ formatDistanceToNow(parseISO(archived.starts_on), {addSuffix: true}) }</div>
|
|
</middle>
|
|
</card>
|
|
{/if}
|
|
{/each}
|
|
{/await}
|
|
</right>
|
|
{:else}
|
|
<right>
|
|
<DiscordReplay />
|
|
</right>
|
|
{/if}
|
|
{/await}
|
|
</container>
|
|
|
|
<Toasts bind:send_toast fade_after={ 10000 } />
|
|
</Layout>
|
|
|