< 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 >