More subtitle progress
This commit is contained in:
Generated
-18
@@ -21,7 +21,6 @@
|
||||
"@capacitor/share": "^8.0.0",
|
||||
"@macfja/serializer": "^1.1.4",
|
||||
"@macfja/svelte-persistent-store": "^2.4.2",
|
||||
"@neodrag/svelte": "^3.0.0-next.8",
|
||||
"altcha": "^2.3.0",
|
||||
"altcha-lib": "^1.4.1",
|
||||
"beercss": "^4.0.2",
|
||||
@@ -3524,23 +3523,6 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@neodrag/core": {
|
||||
"version": "3.0.0-next.8",
|
||||
"resolved": "https://registry.npmjs.org/@neodrag/core/-/core-3.0.0-next.8.tgz",
|
||||
"integrity": "sha512-RNFwbo2w6M7BEzCh7pls68X2h3n+ofFDW1XnGyIzhtJ2bb8J/2VqixOpOlw9NfSJ61xgUC5ymGQbQFg5BKsWBA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@neodrag/svelte": {
|
||||
"version": "3.0.0-next.8",
|
||||
"resolved": "https://registry.npmjs.org/@neodrag/svelte/-/svelte-3.0.0-next.8.tgz",
|
||||
"integrity": "sha512-VkTnOqiHpAaEK5jp2PqQPEEx5AaNGVoKeoC2TuWd2xuteGOVq7NqQjNC4sxk0HAVysfYOC9YhYOsVyRiKo2LFw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@neodrag/core": "3.0.0-next.8",
|
||||
"svelte": "^3 || ^4 || ^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"@capacitor/share": "^8.0.0",
|
||||
"@macfja/serializer": "^1.1.4",
|
||||
"@macfja/svelte-persistent-store": "^2.4.2",
|
||||
"@neodrag/svelte": "^3.0.0-next.8",
|
||||
"altcha": "^2.3.0",
|
||||
"altcha-lib": "^1.4.1",
|
||||
"beercss": "^4.0.2",
|
||||
|
||||
@@ -4,15 +4,11 @@
|
||||
|
||||
const captionTracks: Record<string, string> = {};
|
||||
|
||||
// Configurable settings for captions
|
||||
let captionFontSize: string = $state('1em');
|
||||
let captionColor: string = $state('white');
|
||||
let captionBackgroundColor: string = $state('rgba(0, 0, 0, 0.7)');
|
||||
let captionOpacity: number = $state(1);
|
||||
|
||||
let captionBorderThickness: string = $state('0.1px');
|
||||
let captionBorderColor: string = $state('black');
|
||||
|
||||
export function setTextTrackVisibility(visible: boolean = true) {
|
||||
trackVisible = visible;
|
||||
}
|
||||
@@ -22,23 +18,17 @@
|
||||
fontSize,
|
||||
color,
|
||||
backgroundColor,
|
||||
opacity,
|
||||
borderThickness,
|
||||
borderColor
|
||||
opacity
|
||||
}: {
|
||||
fontSize?: string;
|
||||
color?: string;
|
||||
backgroundColor?: string;
|
||||
opacity?: number;
|
||||
borderThickness?: string;
|
||||
borderColor?: string;
|
||||
}) {
|
||||
if (fontSize) captionFontSize = fontSize;
|
||||
if (color) captionColor = color;
|
||||
if (backgroundColor) captionBackgroundColor = backgroundColor;
|
||||
if (opacity !== undefined) captionOpacity = opacity;
|
||||
if (borderThickness) captionBorderThickness = borderThickness;
|
||||
if (borderColor) captionBorderColor = borderColor;
|
||||
}
|
||||
|
||||
// Fetch caption data for a selected language
|
||||
@@ -52,18 +42,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
captionsCues = (await parseText(await resp.text(), { strict: false })).cues;
|
||||
captionsCues = (await parseText(await resp.text(), { strict: true, type: 'vtt' })).cues;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { draggable, bounds, BoundsFrom, touchAction } from '@neodrag/svelte';
|
||||
import type { VideoPlay } from '$lib/api/model';
|
||||
import { findElementForTime, getPublicEnv } from '$lib/misc';
|
||||
import { decodeHtmlCharCodes, findElementForTime, getPublicEnv } from '$lib/misc';
|
||||
import { invidiousInstanceStore } from '$lib/store';
|
||||
import { onMount } from 'svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { addToast } from '../Toast.svelte';
|
||||
import { parseText, VTTCue } from 'media-captions';
|
||||
import { parseText, type VTTCue } from 'media-captions';
|
||||
|
||||
let {
|
||||
video,
|
||||
@@ -75,7 +64,31 @@
|
||||
showControls: boolean;
|
||||
} = $props();
|
||||
|
||||
let captionElement: HTMLElement | undefined = $state();
|
||||
let captionContainerHeight: number = $state(0);
|
||||
|
||||
let currentCaption: VTTCue | null = $state(null);
|
||||
|
||||
function updateCaptionHeight() {
|
||||
if (captionElement) {
|
||||
captionContainerHeight = captionElement.offsetHeight;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
updateCaptionHeight();
|
||||
|
||||
currentCaption = findElementForTime(
|
||||
captionsCues,
|
||||
currentTime,
|
||||
(cue: VTTCue) => cue.startTime,
|
||||
(cue: VTTCue) => cue.endTime
|
||||
);
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
window.addEventListener('resize', updateCaptionHeight);
|
||||
|
||||
if (video.captions) {
|
||||
for (const caption of video.captions) {
|
||||
let captionUrl: string;
|
||||
@@ -92,39 +105,49 @@
|
||||
}
|
||||
});
|
||||
|
||||
function removeTags(input: string): string {
|
||||
return input.replace(/<[^>]+>/g, '').replace(/&[a-zA-Z0-9#]+;/g, '');
|
||||
}
|
||||
onDestroy(() => {
|
||||
window.removeEventListener('resize', updateCaptionHeight);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if trackVisible && captionsCues.length > 0}
|
||||
{@const currentCaption = findElementForTime(
|
||||
captionsCues,
|
||||
currentTime,
|
||||
(cue: VTTCue) => cue.startTime,
|
||||
(cue: VTTCue) => cue.endTime
|
||||
)}
|
||||
<div
|
||||
{@attach draggable(() => [bounds(BoundsFrom.viewport()), touchAction('manipulation')])}
|
||||
style="z-index: 5;cursor: grab;"
|
||||
>
|
||||
{#if currentCaption?.text}
|
||||
<p
|
||||
style="
|
||||
font-size: {captionFontSize};
|
||||
color: {captionColor};
|
||||
background-color: {captionBackgroundColor};
|
||||
opacity: {captionOpacity};
|
||||
padding: 5px;
|
||||
width: fit-content;
|
||||
border-radius: 0.25rem;
|
||||
user-select: none;
|
||||
-webkit-text-fill-color: {captionColor};
|
||||
-webkit-text-stroke: {captionBorderThickness} {captionBorderColor};
|
||||
"
|
||||
>
|
||||
{removeTags(currentCaption.text)}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if currentCaption && currentTime <= currentCaption.endTime && currentTime >= currentCaption.startTime}
|
||||
<div
|
||||
class="caption-container"
|
||||
bind:this={captionElement}
|
||||
style:top={`calc(${showControls ? 'var(--video-player-height) * 0.85' : 'var(--video-player-height) * 0.98'} - ${captionContainerHeight}px)`}
|
||||
>
|
||||
{#if currentCaption?.text}
|
||||
<p
|
||||
style="
|
||||
font-size: {captionFontSize};
|
||||
color: {captionColor};
|
||||
background-color: {captionBackgroundColor};
|
||||
opacity: {captionOpacity};
|
||||
padding: 5px;
|
||||
border-radius: 0.25rem;
|
||||
user-select: none;
|
||||
"
|
||||
>
|
||||
{decodeHtmlCharCodes(currentCaption.text)}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.caption-container {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.caption-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import type { VideoPlay } from '$lib/api/model';
|
||||
import {
|
||||
invidiousAuthStore,
|
||||
invidiousInstanceStore,
|
||||
isAndroidTvStore,
|
||||
playerAlwaysLoopStore,
|
||||
playerAndroidLockOrientation,
|
||||
@@ -458,31 +457,6 @@
|
||||
const error = (event as CustomEvent).detail as shaka.util.Error;
|
||||
console.error('Player error:', error);
|
||||
});
|
||||
player.getNetworkingEngine()?.registerResponseFilter((type, response) => {
|
||||
if (
|
||||
type !== shaka.net.NetworkingEngine.RequestType.SEGMENT ||
|
||||
!response.uri.includes('/api/timedtext')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(response.uri);
|
||||
|
||||
// Fix positioning for auto-generated subtitles
|
||||
// Credit to Freetube!
|
||||
if (
|
||||
url.hostname.endsWith('.youtube.com') &&
|
||||
url.pathname === '/api/timedtext' &&
|
||||
url.searchParams.get('caps') === 'asr' &&
|
||||
url.searchParams.get('kind') === 'asr' &&
|
||||
url.searchParams.get('fmt') === 'vtt'
|
||||
) {
|
||||
const stringBody = new TextDecoder().decode(response.data);
|
||||
// position:0% for LTR text and position:100% for RTL text
|
||||
const cleaned = stringBody.replaceAll(/ align:start position:(?:10)?0%$/gm, '');
|
||||
response.data = new TextEncoder().encode(cleaned).buffer;
|
||||
}
|
||||
});
|
||||
|
||||
playerElement?.addEventListener('volumechange', saveVolumePreference);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user