Added video previews
This commit is contained in:
@@ -21,7 +21,6 @@
|
||||
import { getDynamicTheme } from './theme';
|
||||
|
||||
export let data: { video: VideoPlay; content: PhasedDescription; playlistId: string | null };
|
||||
export let currentTime: number = 0;
|
||||
export let audioMode = false;
|
||||
export let player: MediaPlayerElement;
|
||||
export let isSyncing: boolean = false;
|
||||
@@ -38,13 +37,6 @@
|
||||
const proxyVideos = get(playerProxyVideos);
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const defaultVolume = localStorage.getItem('volume');
|
||||
if (defaultVolume) {
|
||||
player.volume = Number(defaultVolume);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (!data.video.hlsUrl) {
|
||||
if (data.video.captions) {
|
||||
data.video.captions.forEach(async (caption) => {
|
||||
@@ -81,10 +73,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
player.addEventListener('time-update', () => {
|
||||
currentTime = player.currentTime;
|
||||
});
|
||||
|
||||
player.addEventListener('pause', () => {
|
||||
savePlayerPos();
|
||||
});
|
||||
|
||||
@@ -4,18 +4,24 @@
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
deArrowEnabled,
|
||||
interfacePreviewVideoOnHover,
|
||||
playerProxyVideos,
|
||||
playerSavePlaybackPosition,
|
||||
syncPartyConnections,
|
||||
syncPartyPeer
|
||||
} from '../store';
|
||||
import { getDeArrow, getThumbnail, getVideo } from './Api';
|
||||
import type { Notification, PlaylistPageVideo, Video, VideoBase } from './Api/model';
|
||||
import type { Notification, PlaylistPageVideo, Video, VideoBase, VideoPlay } from './Api/model';
|
||||
import { cleanNumber, proxyVideoUrl, truncate, videoLength } from './misc';
|
||||
import type { PlayerEvents } from './player';
|
||||
|
||||
export let video: VideoBase | Video | Notification | PlaylistPageVideo;
|
||||
export let playlistId: string = '';
|
||||
|
||||
let showVideoPreview: boolean = false;
|
||||
let videoPreview: VideoPlay | null = null;
|
||||
let videoPreviewMuted: boolean = true;
|
||||
|
||||
let watchUrl = new URL(`${import.meta.env.VITE_DEFAULT_FRONTEND_URL}/watch/${video.videoId}`);
|
||||
|
||||
if (playlistId !== '') {
|
||||
@@ -30,7 +36,6 @@
|
||||
|
||||
let loading = true;
|
||||
let loaded = false;
|
||||
let failed = false;
|
||||
|
||||
let img: HTMLImageElement;
|
||||
|
||||
@@ -118,7 +123,6 @@
|
||||
};
|
||||
img.onerror = () => {
|
||||
loading = false;
|
||||
failed = true;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -139,47 +143,100 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function previewVideo() {
|
||||
if (!get(interfacePreviewVideoOnHover)) return;
|
||||
|
||||
showVideoPreview = true;
|
||||
try {
|
||||
videoPreview = await getVideo(video.videoId, get(playerProxyVideos));
|
||||
} catch {
|
||||
showVideoPreview = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<a
|
||||
class="wave"
|
||||
style="width: 100%; overflow: hidden;min-height:100px;"
|
||||
href={watchUrl.toString()}
|
||||
data-sveltekit-preload-data="off"
|
||||
on:click={syncChangeVideo}
|
||||
<div
|
||||
style="position: relative;"
|
||||
on:mouseover={previewVideo}
|
||||
on:mouseleave={() => (showVideoPreview = false)}
|
||||
on:focus={() => {}}
|
||||
role="region"
|
||||
>
|
||||
{#if loading}
|
||||
<progress class="circle"></progress>
|
||||
{:else if loaded}
|
||||
<img
|
||||
class="responsive"
|
||||
style="max-width: 100%;min-height: 160px;"
|
||||
src={img.src}
|
||||
alt="Thumbnail for video"
|
||||
/>
|
||||
{:else}
|
||||
<p>{$_('thumbnail.failedToLoadImage')}</p>
|
||||
{/if}
|
||||
{#if progress}
|
||||
<progress
|
||||
class="absolute right bottom"
|
||||
style="z-index: 1;"
|
||||
value={progress}
|
||||
max={video.lengthSeconds}
|
||||
></progress>
|
||||
{/if}
|
||||
{#if !('liveVideo' in video) || !video.liveVideo}
|
||||
{#if video.lengthSeconds !== 0}
|
||||
<div class="absolute right bottom small-margin black white-text small-text thumbnail-corner">
|
||||
{videoLength(video.lengthSeconds)}
|
||||
<a
|
||||
class="wave thumbnail"
|
||||
href={watchUrl.toString()}
|
||||
data-sveltekit-preload-data="off"
|
||||
on:click={syncChangeVideo}
|
||||
>
|
||||
{#if loading}
|
||||
<progress class="circle"></progress>
|
||||
{:else if loaded}
|
||||
{#if showVideoPreview && videoPreview}
|
||||
<div style="max-width: 100%; max-height: 200px;">
|
||||
<video
|
||||
style="max-width: 100%; max-height: 200px;"
|
||||
autoplay
|
||||
poster={img.src}
|
||||
muted={videoPreviewMuted}
|
||||
volume={0.5}
|
||||
src={videoPreview.formatStreams[0].url}
|
||||
>
|
||||
<track label="" kind="subtitles" srclang="" src="" default />
|
||||
</video>
|
||||
</div>
|
||||
{:else}
|
||||
<img
|
||||
class="responsive"
|
||||
style="max-width: 100%;min-height: 160px;"
|
||||
src={img.src}
|
||||
alt="Thumbnail for video"
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<p>{$_('thumbnail.failedToLoadImage')}</p>
|
||||
{/if}
|
||||
{#if progress}
|
||||
<progress
|
||||
class="absolute right bottom"
|
||||
style="z-index: 1;"
|
||||
value={progress}
|
||||
max={video.lengthSeconds}
|
||||
></progress>
|
||||
{/if}
|
||||
{#if !('liveVideo' in video) || !video.liveVideo}
|
||||
{#if video.lengthSeconds !== 0}
|
||||
<div
|
||||
class="absolute right bottom small-margin black white-text small-text thumbnail-corner"
|
||||
>
|
||||
{videoLength(video.lengthSeconds)}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="absolute right bottom small-margin red white-text small-text thumbnail-corner">
|
||||
{$_('thumbnail.live')}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="absolute right bottom small-margin red white-text small-text thumbnail-corner">
|
||||
{$_('thumbnail.live')}
|
||||
</div>
|
||||
</a>
|
||||
{#if showVideoPreview && videoPreview}
|
||||
<button
|
||||
class="no-padding"
|
||||
style="position: absolute; bottom: 10px; left: 10px; width: 30px; height: 30px;"
|
||||
on:click={() => {
|
||||
videoPreviewMuted = !videoPreviewMuted;
|
||||
}}
|
||||
>
|
||||
<i>
|
||||
{#if videoPreviewMuted}
|
||||
volume_off
|
||||
{:else}
|
||||
volume_up
|
||||
{/if}
|
||||
</i>
|
||||
</button>
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="small-padding">
|
||||
<nav class="no-margin">
|
||||
<div class="max">
|
||||
@@ -200,3 +257,17 @@
|
||||
|
||||
<canvas id="canvas" style="display: none;"></canvas>
|
||||
<div id="video-container" style="display: none;"></div>
|
||||
|
||||
<style>
|
||||
.thumbnail {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
max-height: 160px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.thumbnail {
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
deArrowEnabled,
|
||||
deArrowInstance,
|
||||
deArrowThumbnailInstance,
|
||||
interfacePreviewVideoOnHover,
|
||||
interfaceSearchSuggestions,
|
||||
playerAlwaysLoop,
|
||||
playerAutoPlay,
|
||||
@@ -87,6 +88,11 @@ const persistedStores = [
|
||||
store: interfaceSearchSuggestions,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'previewVideoOnHover',
|
||||
store: interfacePreviewVideoOnHover,
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlock',
|
||||
store: sponsorBlock,
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"color": "Color"
|
||||
},
|
||||
"searchSuggestions": "Search suggestions",
|
||||
"previewVideoOnHover": "Preview video on hover",
|
||||
"dataPreferences": {
|
||||
"dataPreferences": "Data preferences",
|
||||
"content": "Looking to import/export subscriptions, change password or delete account? Click here and scroll to the bottom of the page."
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
deArrowEnabled,
|
||||
deArrowInstance,
|
||||
deArrowThumbnailInstance,
|
||||
interfacePreviewVideoOnHover,
|
||||
interfaceSearchSuggestions,
|
||||
playerAlwaysLoop,
|
||||
playerAutoPlay,
|
||||
@@ -384,6 +385,22 @@
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.previewVideoOnHover')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$interfacePreviewVideoOnHover}
|
||||
on:click={() => interfacePreviewVideoOnHover.set(!$interfacePreviewVideoOnHover)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
let theatreMode = get(playerTheatreModeByDefault);
|
||||
|
||||
let audioMode = get(playerListenByDefault);
|
||||
let currentTime: number;
|
||||
let seekTo: (time: number) => void;
|
||||
let player: MediaPlayerElement;
|
||||
|
||||
@@ -357,14 +356,7 @@
|
||||
<div class="grid">
|
||||
<div class={`s12 m12 l${theatreMode ? '12' : '10'}`}>
|
||||
{#key data.video.videoId}
|
||||
<Player
|
||||
{data}
|
||||
{audioMode}
|
||||
isSyncing={$syncPartyPeer !== null}
|
||||
bind:seekTo
|
||||
bind:currentTime
|
||||
bind:player
|
||||
/>
|
||||
<Player {data} {audioMode} isSyncing={$syncPartyPeer !== null} bind:seekTo bind:player />
|
||||
{/key}
|
||||
|
||||
<h5>{data.video.title}</h5>
|
||||
|
||||
@@ -24,6 +24,7 @@ export const playerAutoplayNextByDefault = persisted('autoplayNextByDefault', fa
|
||||
export const returnYtDislikes = persisted('returnYtDislikes', true);
|
||||
|
||||
export const interfaceSearchSuggestions = persisted('searchSuggestions', true);
|
||||
export const interfacePreviewVideoOnHover = persisted('previewVideoOnHover', true);
|
||||
|
||||
export const auth: Writable<null | { username: string; token: string; }> = persisted(
|
||||
'authToken',
|
||||
|
||||
Reference in New Issue
Block a user