feat: Save audio and subtitles preferences
This commit is contained in:
@@ -9,6 +9,14 @@
|
||||
import { appState } from '../../stores/app-state.store';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { modalStack, modalStackTop } from '../Modal/modal.store';
|
||||
import { createLocalStorageStore } from '../../stores/localstorage.store';
|
||||
import { get } from 'svelte/store';
|
||||
import { ISO_2_LANGUAGES } from '../../utils/iso-2-languages';
|
||||
|
||||
type MediaLanguageStore = {
|
||||
subtitles?: string;
|
||||
audio?: string;
|
||||
};
|
||||
|
||||
export let id: string;
|
||||
export let modalId: symbol;
|
||||
@@ -41,18 +49,24 @@
|
||||
id: string,
|
||||
options: { audioStreamIndex?: number; bitrate?: number; playbackPosition?: number } = {}
|
||||
) {
|
||||
const itemP = jellyfinApi.getLibraryItem(id);
|
||||
const jellyfinPlaybackInfoP = itemP.then((item) =>
|
||||
jellyfinApi.getPlaybackInfo(
|
||||
id,
|
||||
getDeviceProfile(),
|
||||
options.playbackPosition || item?.UserData?.PlaybackPositionTicks || 0,
|
||||
options.bitrate || getQualities(item?.Height || 1080)[0]?.maxBitrate,
|
||||
options.audioStreamIndex || undefined
|
||||
)
|
||||
const item = await jellyfinApi.getLibraryItem(id);
|
||||
|
||||
const mediaLanguagesStore = createLocalStorageStore<MediaLanguageStore>(
|
||||
'media-tracks-' + (item?.SeriesName || id),
|
||||
{}
|
||||
);
|
||||
const storedAudioStreamIndex = item?.MediaStreams?.find(
|
||||
(s) => s.Type === 'Audio' && s.Language === mediaLanguagesStore.get().audio
|
||||
)?.Index;
|
||||
const audioStreamIndex = options.audioStreamIndex ?? storedAudioStreamIndex ?? undefined;
|
||||
|
||||
const jellyfinPlaybackInfo = await jellyfinApi.getPlaybackInfo(
|
||||
id,
|
||||
getDeviceProfile(),
|
||||
options.playbackPosition || item?.UserData?.PlaybackPositionTicks || 0,
|
||||
options.bitrate || getQualities(item?.Height || 1080)[0]?.maxBitrate,
|
||||
audioStreamIndex
|
||||
);
|
||||
const item = await itemP;
|
||||
const jellyfinPlaybackInfo = await jellyfinPlaybackInfoP;
|
||||
|
||||
if (!item || !jellyfinPlaybackInfo) {
|
||||
console.error('No item or playback info', item, jellyfinPlaybackInfo);
|
||||
@@ -70,14 +84,30 @@
|
||||
|
||||
const mediaSource = jellyfinPlaybackInfo.MediaSources?.[0];
|
||||
|
||||
const storedSubtitlesLang = mediaLanguagesStore.get().subtitles;
|
||||
|
||||
if (options.audioStreamIndex) {
|
||||
const audioLang = mediaSource?.MediaStreams?.[options.audioStreamIndex]?.Language;
|
||||
mediaLanguagesStore.update((prev) => ({
|
||||
...prev,
|
||||
audio: audioLang || undefined
|
||||
}));
|
||||
}
|
||||
|
||||
let subtitles: Subtitles | undefined;
|
||||
for (const stream of mediaSource?.MediaStreams || []) {
|
||||
if (stream.Type === 'Subtitle' && stream.IsDefault) {
|
||||
if (
|
||||
stream.Type === 'Subtitle' &&
|
||||
(storedSubtitlesLang !== undefined
|
||||
? stream.Language === storedSubtitlesLang
|
||||
: stream.IsDefault)
|
||||
) {
|
||||
subtitles = {
|
||||
kind: 'subtitles',
|
||||
srclang: stream.Language || '',
|
||||
url: `${$appState.user?.settings.jellyfin.baseUrl}/Videos/${id}/${mediaSource?.Id}/Subtitles/${stream.Index}/${stream.Level}/Stream.vtt`,
|
||||
language: 'English'
|
||||
// @ts-ignore
|
||||
language: ISO_2_LANGUAGES[stream?.Language || '']?.name || 'English'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -90,13 +120,34 @@
|
||||
language: 'English'
|
||||
})) || [];
|
||||
|
||||
const selectSubtitles = (subtitles?: Subtitles) => {
|
||||
mediaLanguagesStore.update((prev) => ({
|
||||
...prev,
|
||||
subtitles: subtitles?.srclang || ''
|
||||
}));
|
||||
|
||||
if (subtitleInfo) {
|
||||
if (subtitles)
|
||||
subtitleInfo = {
|
||||
...subtitleInfo,
|
||||
subtitles
|
||||
};
|
||||
else
|
||||
subtitleInfo = {
|
||||
...subtitleInfo,
|
||||
subtitles: undefined
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
subtitleInfo = {
|
||||
subtitles,
|
||||
availableSubtitles
|
||||
availableSubtitles,
|
||||
selectSubtitles
|
||||
};
|
||||
|
||||
playbackInfo = {
|
||||
audioStreamIndex: options.audioStreamIndex || mediaSource?.DefaultAudioStreamIndex || -1,
|
||||
audioStreamIndex: audioStreamIndex ?? mediaSource?.DefaultAudioStreamIndex ?? -1,
|
||||
audioTracks:
|
||||
mediaSource?.MediaStreams?.filter((s) => s.Type === 'Audio').map((s) => ({
|
||||
index: s.Index || -1,
|
||||
|
||||
@@ -81,10 +81,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: if (subtitleInfo?.subtitles) {
|
||||
console.log('Unpausing because subtitles were set');
|
||||
video.play();
|
||||
}
|
||||
$: subtitleInfo && updateSubtitlesVisibility();
|
||||
const updateSubtitlesVisibility = () => {
|
||||
const tracks = video?.textTracks;
|
||||
for (const track of tracks) {
|
||||
track.mode = track.id === subtitleInfo?.subtitles?.url ? 'showing' : 'disabled';
|
||||
}
|
||||
};
|
||||
// $: if (subtitleInfo?.subtitles) {
|
||||
// console.log('Unpausing because subtitles were set');
|
||||
// video.play();
|
||||
// }
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
@@ -110,13 +117,18 @@
|
||||
crossorigin="anonymous"
|
||||
class="w-full h-full"
|
||||
>
|
||||
{#if subtitleInfo?.subtitles}
|
||||
<track
|
||||
src={subtitleInfo.subtitles.url}
|
||||
kind={subtitleInfo.subtitles.kind}
|
||||
srclang={subtitleInfo.subtitles.srclang}
|
||||
default={true}
|
||||
label={subtitleInfo.subtitles.language}
|
||||
/>
|
||||
{#if subtitleInfo?.availableSubtitles}
|
||||
{#each subtitleInfo.availableSubtitles as subtitle}
|
||||
<track
|
||||
default={subtitle.url === subtitleInfo.subtitles?.url}
|
||||
id={subtitle.url}
|
||||
src={subtitle.url}
|
||||
kind={subtitle.kind}
|
||||
srclang={subtitle.srclang}
|
||||
label={subtitle.language}
|
||||
/>
|
||||
{/each}
|
||||
<!--{:else}-->
|
||||
<!-- <track kind="subtitles" src="" />-->
|
||||
{/if}
|
||||
</video>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import type { Selectable } from '../../selectable';
|
||||
import { modalStack } from '../Modal/modal.store';
|
||||
import SelectSubtitlesModal from './SelectSubtitlesModal.svelte';
|
||||
import { ChatBubble, TextAlignLeft, Update } from 'radix-icons-svelte';
|
||||
import { ChatBubble, TextAlignLeft } from 'radix-icons-svelte';
|
||||
import IconButton from './IconButton.svelte';
|
||||
import SelectAudioModal from './SelectAudioModal.svelte';
|
||||
import Spinner from '../Utils/Spinner.svelte';
|
||||
@@ -58,16 +58,7 @@
|
||||
|
||||
function selectSubtitles(subtitles?: Subtitles) {
|
||||
if (subtitleInfo) {
|
||||
if (subtitles)
|
||||
subtitleInfo = {
|
||||
...subtitleInfo,
|
||||
subtitles
|
||||
};
|
||||
else
|
||||
subtitleInfo = {
|
||||
...subtitleInfo,
|
||||
subtitles: undefined
|
||||
};
|
||||
subtitleInfo.selectSubtitles(subtitles);
|
||||
} else {
|
||||
console.error('No subtitle info when selecting subtitles');
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import VideoPlayerModal from './JellyfinVideoPlayerModal.svelte';
|
||||
export type SubtitleInfo = {
|
||||
subtitles?: Subtitles;
|
||||
availableSubtitles: Subtitles[];
|
||||
selectSubtitles: (subtitles?: Subtitles) => void;
|
||||
};
|
||||
|
||||
export type Subtitles = {
|
||||
|
||||
@@ -5,6 +5,7 @@ export function createLocalStorageStore<T>(key: string, defaultValue: T) {
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
get: () => get(store),
|
||||
set: (value: T) => {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
store.set(value);
|
||||
|
||||
Reference in New Issue
Block a user