Added chapter support
This commit is contained in:
@@ -91,6 +91,17 @@ export interface VideoPlay extends Video {
|
||||
recommendedVideos: VideoBase[];
|
||||
authorThumbnails: Image[];
|
||||
captions: Captions[];
|
||||
storyboards?: {
|
||||
url: string;
|
||||
templateUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
count: number;
|
||||
interval: number;
|
||||
storyboardWidth: number;
|
||||
storyboardHeight: number;
|
||||
storyboardCount: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ReturnYTDislikes {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let classes = '';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="m l"
|
||||
class={classes}
|
||||
height="100%"
|
||||
version="1.0"
|
||||
viewBox="0 0 512 512"
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -22,10 +22,18 @@
|
||||
import { getDynamicTheme } from './theme';
|
||||
|
||||
export let data: { video: VideoPlay };
|
||||
export let currentTime: number = 0;
|
||||
|
||||
export function seekTo(time: number) {
|
||||
if (typeof player !== 'undefined') {
|
||||
player.currentTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
let player: Plyr | undefined = undefined;
|
||||
|
||||
let categoryBeingSkipped = '';
|
||||
|
||||
let player: Plyr | undefined;
|
||||
let sponsorBlock: SponsorBlock | undefined;
|
||||
let hls: Hls | undefined;
|
||||
let dash: MediaPlayerClass | undefined;
|
||||
@@ -85,6 +93,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
player.on('timeupdate', (event: PlyrEvent) => {
|
||||
currentTime = event.detail.plyr.currentTime;
|
||||
});
|
||||
|
||||
player.autoplay = get(playerAutoPlay);
|
||||
player.loop = get(playerAlwaysLoop);
|
||||
|
||||
|
||||
@@ -11,4 +11,49 @@ export function numberWithCommas(number: number) {
|
||||
|
||||
export function cleanNumber(number: number): string {
|
||||
return humanNumber(number, (number: number) => Number.parseFloat(number.toString()).toFixed(1));
|
||||
}
|
||||
|
||||
export function phaseDescription(content: string): {
|
||||
description: string, timestamps: { title: string; time: number; timePretty: string; }[];
|
||||
} {
|
||||
const timestamps: { title: string; time: number; timePretty: string; }[] = [];
|
||||
const lines = content.split('\n');
|
||||
|
||||
const regex = /(\d+:\d+(?::\d+)?)(?:\s(.+))?/;
|
||||
const filteredLines = lines.filter(line => {
|
||||
const match = regex.exec(line);
|
||||
|
||||
if (match !== null) {
|
||||
const timestamp = match[1];
|
||||
const title = match[2] || '';
|
||||
timestamps.push({
|
||||
time: convertToSeconds(timestamp),
|
||||
title: title,
|
||||
timePretty: timestamp
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const filteredContent = filteredLines.join('\n');
|
||||
|
||||
return { description: filteredContent, timestamps: timestamps };
|
||||
}
|
||||
|
||||
function convertToSeconds(time: string): number {
|
||||
const parts = time.split(':').map(part => parseInt(part));
|
||||
let seconds = 0;
|
||||
if (parts.length === 3) {
|
||||
// hh:mm:ss
|
||||
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
|
||||
} else if (parts.length === 2) {
|
||||
// hh:ss or m:ss
|
||||
seconds = parts[0] * 60 + parts[1];
|
||||
} else if (parts.length === 1) {
|
||||
// s
|
||||
seconds = parts[0];
|
||||
}
|
||||
return seconds;
|
||||
}
|
||||
@@ -219,7 +219,7 @@
|
||||
><i>menu</i></button
|
||||
>
|
||||
|
||||
<Logo />
|
||||
<Logo classes="m l" />
|
||||
<h6 class="m l">Materialious</h6>
|
||||
|
||||
<div class="max"></div>
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
import Player from '$lib/Player.svelte';
|
||||
import Thumbnail from '$lib/Thumbnail.svelte';
|
||||
import { cleanNumber, numberWithCommas } from '$lib/misc.js';
|
||||
import type Plyr from 'plyr';
|
||||
import { activePage } from '../../../store.js';
|
||||
|
||||
export let data;
|
||||
|
||||
let player: Plyr | undefined;
|
||||
|
||||
activePage.set(null);
|
||||
|
||||
async function toggleSubscribed() {
|
||||
@@ -19,13 +22,16 @@
|
||||
|
||||
data.subscribed = !data.subscribed;
|
||||
}
|
||||
|
||||
let currentTime: number;
|
||||
let seekTo: (time: number) => void;
|
||||
</script>
|
||||
|
||||
{#if data}
|
||||
<div class="grid">
|
||||
<div class="s12 m12 l10">
|
||||
{#key data.video.videoId}
|
||||
<Player {data} />
|
||||
<Player {data} bind:seekTo bind:currentTime />
|
||||
{/key}
|
||||
|
||||
<h5>{data.video.title}</h5>
|
||||
@@ -116,11 +122,22 @@
|
||||
<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>
|
||||
<p style="white-space: pre-line;word-wrap: break-word;">{data.content.description}</p>
|
||||
{#if data.content}
|
||||
<h6 style="margin-bottom: .3em;">Chapters</h6>
|
||||
{#each data.content.timestamps as timestamp}
|
||||
<button
|
||||
on:click={() => seekTo(timestamp.time)}
|
||||
class="timestamps"
|
||||
class:primary={timestamp.time <= currentTime}
|
||||
>{timestamp.title} - {timestamp.timePretty}</button
|
||||
>
|
||||
{/each}
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
<div class="space"></div>
|
||||
{#if data.comments && data.comments.comments.length > 0}
|
||||
{#if data.comments && typeof data.comments.comments !== 'undefined' && data.comments.comments.length > 0}
|
||||
<h6>{numberWithCommas(data.comments.commentCount)} comments</h6>
|
||||
{#each data.comments.comments as comment}
|
||||
<div class="comment">
|
||||
@@ -169,44 +186,54 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="s12 m12 l2">
|
||||
{#each data.video.recommendedVideos as recommendedVideo}
|
||||
<article class="no-padding">
|
||||
<Thumbnail video={recommendedVideo} />
|
||||
</article>
|
||||
{/each}
|
||||
{#if data.video.recommendedVideos}
|
||||
{#each data.video.recommendedVideos as recommendedVideo}
|
||||
<article class="no-padding">
|
||||
<Thumbnail video={recommendedVideo} />
|
||||
</article>
|
||||
{/each}
|
||||
{/if}
|
||||
</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>
|
||||
{:else}
|
||||
<PageLoading />
|
||||
{/if}
|
||||
|
||||
<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;
|
||||
}
|
||||
}
|
||||
|
||||
.timestamps {
|
||||
margin-left: 0;
|
||||
margin-bottom: 0.4em;
|
||||
display: block;
|
||||
background-color: var(--secondary-container);
|
||||
color: var(--on-secondary-container);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { amSubscribed, getComments, getDislikes, getVideo, postHistory } from '$lib/Api/index.js';
|
||||
import { phaseDescription } from '$lib/misc';
|
||||
import { get } from 'svelte/store';
|
||||
import { auth, playerProxyVideos } from '../../../store';
|
||||
|
||||
@@ -11,6 +12,7 @@ export async function load({ params }) {
|
||||
video: video,
|
||||
returnYTDislikes: await getDislikes(params.slug),
|
||||
comments: video.liveNow ? null : await getComments(params.slug, { sort_by: "top", source: "youtube" }),
|
||||
subscribed: await amSubscribed(video.authorId)
|
||||
subscribed: await amSubscribed(video.authorId),
|
||||
content: phaseDescription(video.description)
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user