Added saving player pos
This commit is contained in:
@@ -1,16 +1,33 @@
|
||||
<script lang="ts">
|
||||
import Plyr from 'plyr';
|
||||
import Plyr, { type PlyrEvent } from 'plyr';
|
||||
import 'plyr/dist/plyr.css';
|
||||
import { onDestroy, onMount, tick } from 'svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
import { invidiousInstance } from '../store';
|
||||
import {
|
||||
invidiousInstance,
|
||||
playerAlwaysLoop,
|
||||
playerAutoPlay,
|
||||
playerSavePlaybackPosition
|
||||
} from '../store';
|
||||
import type { VideoPlay } from './Api/model';
|
||||
export let data: { video: VideoPlay };
|
||||
|
||||
let player: Plyr | undefined;
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
const playerPos = localStorage.getItem(data.video.videoId);
|
||||
|
||||
player = new Plyr('#player');
|
||||
|
||||
player.on('loadeddata', (event: PlyrEvent) => {
|
||||
if (get(playerSavePlaybackPosition) && playerPos) {
|
||||
event.detail.plyr.currentTime = Number(playerPos);
|
||||
}
|
||||
});
|
||||
|
||||
player.autoplay = get(playerAutoPlay);
|
||||
player.loop = get(playerAlwaysLoop);
|
||||
|
||||
player.source = {
|
||||
type: 'video',
|
||||
previewThumbnails: {
|
||||
@@ -32,9 +49,11 @@
|
||||
});
|
||||
|
||||
onDestroy(async () => {
|
||||
if (get(playerSavePlaybackPosition) && player?.currentTime) {
|
||||
localStorage.setItem(data.video.videoId, player.currentTime.toString());
|
||||
}
|
||||
|
||||
player = undefined;
|
||||
await tick();
|
||||
console.log(document);
|
||||
document.getElementsByClassName('plyr')[0]?.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
<script lang="ts">
|
||||
import Logo from '$lib/Logo.svelte';
|
||||
|
||||
import 'beercss';
|
||||
import 'material-dynamic-colors';
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { darkMode, themeColor } from '../store';
|
||||
import {
|
||||
activePage,
|
||||
darkMode,
|
||||
playerAlwaysLoop,
|
||||
playerAutoPlay,
|
||||
playerDash,
|
||||
playerListenByDefault,
|
||||
playerProxyVideos,
|
||||
playerSavePlaybackPosition,
|
||||
themeColor
|
||||
} from '../store';
|
||||
|
||||
let currentPage: string | null = '';
|
||||
activePage.subscribe((page) => (currentPage = page));
|
||||
|
||||
let autoplay = false;
|
||||
let alwaysLoop = false;
|
||||
let proxyVideos = false;
|
||||
let savePlayerPackPos = false;
|
||||
let dash = false;
|
||||
let listenByDefault = false;
|
||||
|
||||
playerAutoPlay.subscribe((value) => (autoplay = value));
|
||||
playerAlwaysLoop.subscribe((value) => (alwaysLoop = value));
|
||||
playerProxyVideos.subscribe((value) => (proxyVideos = value));
|
||||
playerSavePlaybackPosition.subscribe((value) => (savePlayerPackPos = value));
|
||||
playerDash.subscribe((value) => (dash = value));
|
||||
playerListenByDefault.subscribe((value) => (listenByDefault = value));
|
||||
|
||||
const pages = [
|
||||
{
|
||||
@@ -91,7 +117,7 @@
|
||||
<nav class="left m l small">
|
||||
<header></header>
|
||||
{#each pages as page}
|
||||
<a href={page.href}
|
||||
<a href={page.href} class:active={currentPage === page.name.toLowerCase()}
|
||||
><i>{page.icon}</i>
|
||||
<div>{page.name}</div>
|
||||
</a>
|
||||
@@ -120,24 +146,123 @@
|
||||
|
||||
<dialog class="right" id="dialog-settings">
|
||||
<nav>
|
||||
<h5 class="max">Settings</h5>
|
||||
<h4 class="max">Settings</h4>
|
||||
<button class="circle transparent" data-ui="#dialog-settings"><i>close</i></button>
|
||||
</nav>
|
||||
<p>Customize Materialious</p>
|
||||
<button on:click={toggleDarkMode} class="no-margin">
|
||||
{#if !$darkMode}
|
||||
<i>dark_mode</i>
|
||||
<span>Dark mode</span>
|
||||
{:else}
|
||||
<i>light_mode</i>
|
||||
<span>Light mode</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button>
|
||||
<i>palette</i>
|
||||
<span>Color</span>
|
||||
<input on:change={setColor} type="color" />
|
||||
</button>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Theme</h6>
|
||||
<button on:click={toggleDarkMode} class="no-margin">
|
||||
{#if !$darkMode}
|
||||
<i>dark_mode</i>
|
||||
<span>Dark mode</span>
|
||||
{:else}
|
||||
<i>light_mode</i>
|
||||
<span>Light mode</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button>
|
||||
<i>palette</i>
|
||||
<span>Color</span>
|
||||
<input on:change={setColor} type="color" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Player</h6>
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>Autoplay</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={autoplay}
|
||||
on:click={() => playerAutoPlay.set(!autoplay)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>Always loop</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={alwaysLoop}
|
||||
on:click={() => playerAlwaysLoop.set(!alwaysLoop)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>Proxy videos</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={proxyVideos}
|
||||
on:click={() => playerProxyVideos.set(!proxyVideos)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>Save playback position</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={savePlayerPackPos}
|
||||
on:click={() => playerSavePlaybackPosition.set(!savePlayerPackPos)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>Listen by default</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={listenByDefault}
|
||||
on:click={() => playerListenByDefault.set(!listenByDefault)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>Dash</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" bind:checked={dash} on:click={() => playerDash.set(!dash)} />
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="right" id="dialog-notifications">
|
||||
@@ -174,4 +299,8 @@
|
||||
nav.left a {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.settings h6 {
|
||||
margin: 1em 0 0.3em 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { cleanNumber } from '$lib/misc';
|
||||
import Thumbnail from '$lib/Thumbnail.svelte';
|
||||
import { activePage } from '../../../store.js';
|
||||
|
||||
export let data;
|
||||
|
||||
activePage.set(null);
|
||||
</script>
|
||||
|
||||
<div class="padding">
|
||||
|
||||
+4
-1
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import PageLoading from '$lib/PageLoading.svelte';
|
||||
import Thumbnail from '$lib/Thumbnail.svelte';
|
||||
import { activePage } from '../../store.js';
|
||||
|
||||
export let data;
|
||||
|
||||
activePage.set('trending');
|
||||
</script>
|
||||
|
||||
{#if data.trending}
|
||||
{#if data}
|
||||
<div class="page right active">
|
||||
<div class="space"></div>
|
||||
<div class="grid">
|
||||
@@ -1,36 +1,61 @@
|
||||
<script lang="ts">
|
||||
import PageLoading from '$lib/PageLoading.svelte';
|
||||
import Player from '$lib/Player.svelte';
|
||||
import Thumbnail from '$lib/Thumbnail.svelte';
|
||||
import { cleanNumber, numberWithCommas } from '$lib/misc.js';
|
||||
import { activePage } from '../../../store.js';
|
||||
|
||||
export let data;
|
||||
|
||||
activePage.set(null);
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<div class="s12 m12 l10">
|
||||
{#key data.video.videoId}
|
||||
<Player {data} />
|
||||
{/key}
|
||||
{#if data}
|
||||
<div class="grid">
|
||||
<div class="s12 m12 l10">
|
||||
{#key data.video.videoId}
|
||||
<Player {data} />
|
||||
{/key}
|
||||
|
||||
<h5>{data.video.title}</h5>
|
||||
<h5>{data.video.title}</h5>
|
||||
|
||||
<nav>
|
||||
<a href={`/channel/${data.video.authorId}`}>
|
||||
<nav>
|
||||
<img
|
||||
class="circle large"
|
||||
src={data.video.authorThumbnails[2].url}
|
||||
alt="Channel profile"
|
||||
/>
|
||||
<div>
|
||||
<p class="bold">{data.video.author}</p>
|
||||
<p>{data.video.subCountText}</p>
|
||||
</div>
|
||||
<nav>
|
||||
<a href={`/channel/${data.video.authorId}`}>
|
||||
<nav>
|
||||
<img
|
||||
class="circle large"
|
||||
src={data.video.authorThumbnails[2].url}
|
||||
alt="Channel profile"
|
||||
/>
|
||||
<div>
|
||||
<p class="bold">{data.video.author}</p>
|
||||
<p>{data.video.subCountText}</p>
|
||||
</div>
|
||||
</nav>
|
||||
</a>
|
||||
<button class="inverse-surface">Subscribe</button>
|
||||
<div class="max"></div>
|
||||
<nav class="no-space m l">
|
||||
<button style="cursor: default;" class="border left-round">
|
||||
<i class="small">thumb_up</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.likes)}</span>
|
||||
</button>
|
||||
<button style="cursor: default;" class="border right-round">
|
||||
<i class="small">thumb_down_alt</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.dislikes)}</span>
|
||||
</button>
|
||||
</nav>
|
||||
</a>
|
||||
<button class="inverse-surface">Subscribe</button>
|
||||
<div class="max"></div>
|
||||
<nav class="no-space m l">
|
||||
<button class="border m l" data-ui="#share"
|
||||
><i>share</i> Share
|
||||
<menu class="left no-wrap" id="share" data-ui="#share">
|
||||
<a class="row"> <div class="min">Copy Materialious link</div></a><a class="row">
|
||||
<div class="min">Copy Invidious redirect link</div></a
|
||||
><a class="row"> <div class="min">Copy Youtube link</div></a></menu
|
||||
></button
|
||||
>
|
||||
</nav>
|
||||
|
||||
<nav class="no-space s">
|
||||
<button style="cursor: default;" class="border left-round">
|
||||
<i class="small">thumb_up</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.likes)}</span>
|
||||
@@ -40,113 +65,100 @@
|
||||
<span>{cleanNumber(data.returnYTDislikes.dislikes)}</span>
|
||||
</button>
|
||||
</nav>
|
||||
<button class="border m l" data-ui="#share"
|
||||
><i>share</i> Share
|
||||
<menu class="left no-wrap" id="share" data-ui="#share">
|
||||
<a class="row"> <div class="min">Copy Materialious link</div></a><a class="row">
|
||||
<div class="min">Copy Invidious redirect link</div></a
|
||||
><a class="row"> <div class="min">Copy Youtube link</div></a></menu
|
||||
></button
|
||||
>
|
||||
</nav>
|
||||
|
||||
<nav class="no-space s">
|
||||
<button style="cursor: default;" class="border left-round">
|
||||
<i class="small">thumb_up</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.likes)}</span>
|
||||
</button>
|
||||
<button style="cursor: default;" class="border right-round">
|
||||
<i class="small">thumb_down_alt</i>
|
||||
<span>{cleanNumber(data.returnYTDislikes.dislikes)}</span>
|
||||
</button>
|
||||
</nav>
|
||||
<article class="medium scroll">
|
||||
<p class="bold">
|
||||
{numberWithCommas(data.video.viewCount)} views • {data.video.publishedText}
|
||||
</p>
|
||||
<p style="white-space: pre-line;word-wrap: break-word;">{data.video.description}</p>
|
||||
</article>
|
||||
|
||||
<article class="medium scroll">
|
||||
<p class="bold">
|
||||
{numberWithCommas(data.video.viewCount)} views • {data.video.publishedText}
|
||||
</p>
|
||||
<p style="white-space: pre-line;word-wrap: break-word;">{data.video.description}</p>
|
||||
</article>
|
||||
|
||||
<div class="space"></div>
|
||||
<h6>{numberWithCommas(data.comments.commentCount)} comments</h6>
|
||||
{#each data.comments.comments as comment}
|
||||
<div class="comment">
|
||||
<img class="circle small" src={comment.authorThumbnails[1].url} alt="comment profile" />
|
||||
<div>
|
||||
<div class="row">
|
||||
<p>
|
||||
<span class:bold={true} class:channel-owner={comment.authorIsChannelOwner}
|
||||
>{comment.author}</span
|
||||
>
|
||||
<span class="secondary-text">{comment.publishedText}</span>
|
||||
</p>
|
||||
{#if comment.isPinned}
|
||||
<i>push_pin</i>
|
||||
{/if}
|
||||
{#if comment.isEdited}
|
||||
<i>edit</i>
|
||||
{/if}
|
||||
</div>
|
||||
<p>
|
||||
{comment.content}
|
||||
</p>
|
||||
<div style="display: flex;">
|
||||
<p><i>thumb_up</i> {numberWithCommas(comment.likeCount)}</p>
|
||||
{#if comment.creatorHeart}
|
||||
<div>
|
||||
<img
|
||||
class="circle"
|
||||
style="width: 25px; height: 25px"
|
||||
src={comment.creatorHeart.creatorThumbnail}
|
||||
alt="Creator profile"
|
||||
/>
|
||||
<i
|
||||
style="font-size: 20px;margin-left: 5px;"
|
||||
class="absolute left red-text bottom fill">favorite</i
|
||||
>
|
||||
<div class="space"></div>
|
||||
{#if data.comments.comments}
|
||||
<h6>{numberWithCommas(data.comments.commentCount)} comments</h6>
|
||||
{#each data.comments.comments as comment}
|
||||
<div class="comment">
|
||||
<img class="circle small" src={comment.authorThumbnails[1].url} alt="comment profile" />
|
||||
<div>
|
||||
<div class="row">
|
||||
<p>
|
||||
<span class:bold={true} class:channel-owner={comment.authorIsChannelOwner}
|
||||
>{comment.author}</span
|
||||
>
|
||||
<span class="secondary-text">{comment.publishedText}</span>
|
||||
</p>
|
||||
{#if comment.isPinned}
|
||||
<i>push_pin</i>
|
||||
{/if}
|
||||
{#if comment.isEdited}
|
||||
<i>edit</i>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<p>
|
||||
{comment.content}
|
||||
</p>
|
||||
<div style="display: flex;">
|
||||
<p><i>thumb_up</i> {numberWithCommas(comment.likeCount)}</p>
|
||||
{#if comment.creatorHeart}
|
||||
<div>
|
||||
<img
|
||||
class="circle"
|
||||
style="width: 25px; height: 25px"
|
||||
src={comment.creatorHeart.creatorThumbnail}
|
||||
alt="Creator profile"
|
||||
/>
|
||||
<i
|
||||
style="font-size: 20px;margin-left: 5px;"
|
||||
class="absolute left red-text bottom fill">favorite</i
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<button class="margin">Load more</button>
|
||||
{/each}
|
||||
<button class="margin">Load more</button>
|
||||
{:else}
|
||||
<h6>Comments disabled</h6>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="s12 m12 l2">
|
||||
{#each data.video.recommendedVideos as recommendedVideo}
|
||||
<Thumbnail video={recommendedVideo} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="s12 m12 l2">
|
||||
{#each data.video.recommendedVideos as recommendedVideo}
|
||||
<Thumbnail video={recommendedVideo} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--plyr-color-main: var(--primary);
|
||||
}
|
||||
|
||||
.comment {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.comment img {
|
||||
margin: 0.5em 1em 0 1em;
|
||||
}
|
||||
|
||||
.grid {
|
||||
padding: 1em 10em;
|
||||
}
|
||||
|
||||
.channel-owner {
|
||||
background-color: var(--primary);
|
||||
padding: 0 0.5em;
|
||||
border-radius: 1em;
|
||||
color: var(--surface-variant);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1646px) {
|
||||
.grid {
|
||||
padding: 0;
|
||||
<style>
|
||||
:root {
|
||||
--plyr-color-main: var(--primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
.comment {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.comment img {
|
||||
margin: 0.5em 1em 0 1em;
|
||||
}
|
||||
|
||||
.grid {
|
||||
padding: 1em 10em;
|
||||
}
|
||||
|
||||
.channel-owner {
|
||||
background-color: var(--primary);
|
||||
padding: 0 0.5em;
|
||||
border-radius: 1em;
|
||||
color: var(--surface-variant);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1646px) {
|
||||
.grid {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{:else}
|
||||
<PageLoading />
|
||||
{/if}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
|
||||
export const invidiousInstance = persisted('invidiousInstance', import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE);
|
||||
export const returnYTDislikesInstance = persisted('returnYTDislikesInstance', import.meta.env.VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE);
|
||||
export const darkMode: Writable<null | boolean> = persisted("darkMode", null);
|
||||
export const themeColor: Writable<null | string> = persisted("themeColor", null);
|
||||
export const themeColor: Writable<null | string> = persisted("themeColor", null);
|
||||
|
||||
export const activePage: Writable<string | null> = writable("home");
|
||||
|
||||
export const playerAutoPlay = persisted("autoPlay", false);
|
||||
export const playerAlwaysLoop = persisted("alwaysLoop", false);
|
||||
export const playerProxyVideos = persisted("proxyVideos", false);
|
||||
export const playerListenByDefault = persisted("listenByDefault", false);
|
||||
export const playerSavePlaybackPosition = persisted("savePlaybackPosition", true);
|
||||
export const playerDash = persisted("dash", true);
|
||||
|
||||
Reference in New Issue
Block a user