mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2026-09-02 21:04:08 +03:00
@@ -28,9 +28,7 @@ const { loading } = await useApi(getUserLibraryApi, methodToExecute, { skipCache
|
||||
}));
|
||||
|
||||
const isFavorite = computed({
|
||||
get() {
|
||||
return item.UserData?.IsFavorite ?? false;
|
||||
},
|
||||
get: () => item.UserData?.IsFavorite ?? false,
|
||||
set(newValue) {
|
||||
methodToExecute.value = newValue ? 'markFavoriteItem' : 'unmarkFavoriteItem';
|
||||
}
|
||||
|
||||
@@ -34,9 +34,7 @@ const { loading } = await useApi(getPlaystateApi, methodToExecute, { skipCache:
|
||||
}));
|
||||
|
||||
const isPlayed = computed({
|
||||
get() {
|
||||
return item.UserData?.Played;
|
||||
},
|
||||
get: () => item.UserData?.Played,
|
||||
set(newValue) {
|
||||
methodToExecute.value = newValue ? 'markPlayedItem' : 'markUnplayedItem';
|
||||
}
|
||||
|
||||
@@ -115,9 +115,9 @@ const playbackSpeed = computed({
|
||||
|
||||
if (isUndef(_playbackSpeed.value)) {
|
||||
return playbackSpeedIndex === -1 ? String(playbackManager.playbackSpeed.value) : playbackItems.value[playbackSpeedIndex];
|
||||
} else {
|
||||
return _playbackSpeed.value;
|
||||
}
|
||||
|
||||
return _playbackSpeed.value;
|
||||
},
|
||||
set: (val: PlaybackSpeedValue) => {
|
||||
_playbackSpeed.value = val;
|
||||
|
||||
@@ -63,9 +63,7 @@ const state = reactive<ConfirmDialogState>({
|
||||
|
||||
const { isRevealed, reveal, confirm, cancel } = vUseConfirmDialog();
|
||||
const model = computed({
|
||||
get() {
|
||||
return isRevealed.value;
|
||||
},
|
||||
get: () => isRevealed.value,
|
||||
set(newVal) {
|
||||
if (!newVal) {
|
||||
cancel();
|
||||
|
||||
@@ -125,7 +125,9 @@ const cardSubtitle = computed(() => {
|
||||
case BaseItemKind.Series: {
|
||||
if (item.Status === 'Continuing' && !isNil(item.ProductionYear)) {
|
||||
return `${item.ProductionYear} - ${t('present')}`;
|
||||
} else if (item.EndDate) {
|
||||
}
|
||||
|
||||
if (item.EndDate) {
|
||||
const endYear = new Date(item.EndDate).toLocaleString('en-us', {
|
||||
year: 'numeric'
|
||||
});
|
||||
@@ -169,7 +171,9 @@ const cardSubtitleLink = computed(() => {
|
||||
&& item.AlbumArtists?.length
|
||||
) {
|
||||
return getItemDetailsLink(item.AlbumArtists[0], 'MusicArtist');
|
||||
} else if (item.Type === BaseItemKind.Episode) {
|
||||
}
|
||||
|
||||
if (item.Type === BaseItemKind.Episode) {
|
||||
return getItemDetailsLink(item);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -118,9 +118,7 @@ const parent = getCurrentInstance()?.parent;
|
||||
* the same screen
|
||||
*/
|
||||
const show = computed({
|
||||
get() {
|
||||
return instanceId === openMenu.value;
|
||||
},
|
||||
get: () => instanceId === openMenu.value,
|
||||
set(newVal: boolean) {
|
||||
openMenu.value = newVal ? instanceId : undefined;
|
||||
}
|
||||
@@ -313,7 +311,6 @@ const copyDownloadURLAction = {
|
||||
icon: 'i-mdi:content-copy',
|
||||
action: async (): Promise<void> => {
|
||||
const clipboard = useClipboard();
|
||||
let streamUrls: Map<string, string> | string | undefined;
|
||||
|
||||
if (!clipboard.isSupported.value) {
|
||||
useSnackbar(t('clipboardUnsupported'), 'error');
|
||||
@@ -321,6 +318,8 @@ const copyDownloadURLAction = {
|
||||
return;
|
||||
}
|
||||
|
||||
let streamUrls: Map<string, string> | string | undefined;
|
||||
|
||||
if (item.Id) {
|
||||
switch (item.Type) {
|
||||
case 'Season': {
|
||||
@@ -342,7 +341,7 @@ const copyDownloadURLAction = {
|
||||
*/
|
||||
const text
|
||||
= streamUrls instanceof Map
|
||||
? [...streamUrls.entries()]
|
||||
? [...streamUrls]
|
||||
.map(([k, v]) => `(${k}) - ${v}`)
|
||||
.join('\n')
|
||||
: streamUrls;
|
||||
|
||||
@@ -91,7 +91,9 @@ function getTrackSubtitle(track: MediaStream): string | undefined {
|
||||
getLocaleName(track.Language, i18next.language)
|
||||
?? `${t('unknown')} (${track.Language})`
|
||||
);
|
||||
} else if (type === 'Audio' || type === 'Subtitle') {
|
||||
}
|
||||
|
||||
if (type === 'Audio' || type === 'Subtitle') {
|
||||
return t('undefined');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,9 +267,7 @@ const contentOption = ref<ContentOption>();
|
||||
const contentType = ref<string>();
|
||||
const isImageDialogVisible = shallowRef<boolean>(false);
|
||||
const genresModel = computed({
|
||||
get() {
|
||||
return metadata.value?.Genres ?? undefined;
|
||||
},
|
||||
get: () => metadata.value?.Genres ?? undefined,
|
||||
set(newVal) {
|
||||
if (isArray(newVal) && metadata.value) {
|
||||
metadata.value.Genres = newVal;
|
||||
@@ -277,9 +275,7 @@ const genresModel = computed({
|
||||
}
|
||||
});
|
||||
const tagsModel = computed({
|
||||
get() {
|
||||
return metadata.value?.Tags ?? undefined;
|
||||
},
|
||||
get: () => metadata.value?.Tags ?? undefined,
|
||||
set(newVal) {
|
||||
if (isArray(newVal) && metadata.value) {
|
||||
metadata.value.Tags = newVal;
|
||||
@@ -306,10 +302,12 @@ const dateCreated = computed(() => {
|
||||
const tagLine = computed({
|
||||
get: () => metadata.value?.Taglines?.[0] ?? '',
|
||||
set: (v) => {
|
||||
if (metadata.value) {
|
||||
metadata.value.Taglines ??= [];
|
||||
metadata.value.Taglines[0] = v;
|
||||
if (!metadata.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
metadata.value.Taglines ??= [];
|
||||
metadata.value.Taglines[0] = v;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -23,9 +23,7 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const searchQuery = computed({
|
||||
get(): string {
|
||||
return route.query.q?.toString() ?? '';
|
||||
},
|
||||
get: (): string => route.query.q?.toString() ?? '',
|
||||
set(value) {
|
||||
void router.replace(
|
||||
defu(
|
||||
@@ -40,7 +38,7 @@ const searchQuery = computed({
|
||||
* Handle page redirects depending on the focus state of the component
|
||||
*/
|
||||
async function onFocus(focused: boolean): Promise<void> {
|
||||
if (!searchQuery.value && !focused && globalThis.history.length) {
|
||||
if (!searchQuery.value && !focused && history.length) {
|
||||
router.back();
|
||||
} else if (focused && !searchQuery.value) {
|
||||
await router.push({ path: '/search' });
|
||||
|
||||
@@ -85,29 +85,35 @@ onMounted(() => {
|
||||
* Handle slide changes
|
||||
*/
|
||||
function onSlideChange(): void {
|
||||
if (swiperInstance.value) {
|
||||
currentIndex.value = swiperInstance.value.realIndex;
|
||||
emit('on-slide-change', currentIndex.value, swiperInstance.value);
|
||||
if (!swiperInstance.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentIndex.value = swiperInstance.value.realIndex;
|
||||
emit('on-slide-change', currentIndex.value, swiperInstance.value);
|
||||
}
|
||||
/**
|
||||
* Handle touch events
|
||||
*/
|
||||
function onTouch(): void {
|
||||
if (swiperInstance.value) {
|
||||
isPaused.value = !isPaused.value;
|
||||
emit('on-touch', isPaused.value, swiperInstance.value);
|
||||
if (!swiperInstance.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isPaused.value = !isPaused.value;
|
||||
emit('on-touch', isPaused.value, swiperInstance.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle animation end from progress bars
|
||||
*/
|
||||
function onAnimationEnd(): void {
|
||||
if (swiperInstance.value) {
|
||||
swiperInstance.value.allowSlideNext = true;
|
||||
swiperInstance.value.slideNext();
|
||||
if (!swiperInstance.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
swiperInstance.value.allowSlideNext = true;
|
||||
swiperInstance.value.slideNext();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,14 +48,12 @@ const itemLink = computed(() => getItemDetailsLink(item));
|
||||
const titleString = computed(() => {
|
||||
if (item.Type === BaseItemKind.MusicAlbum && item.AlbumArtist) {
|
||||
return item.AlbumArtist;
|
||||
} else if (
|
||||
item.Type === BaseItemKind.Episode
|
||||
&& item.SeriesName
|
||||
) {
|
||||
return item.SeriesName;
|
||||
} else {
|
||||
return item.Name;
|
||||
}
|
||||
|
||||
return item.Type === BaseItemKind.Episode
|
||||
&& item.SeriesName
|
||||
? item.SeriesName
|
||||
: item.Name;
|
||||
});
|
||||
|
||||
const logoLink = computed(() => {
|
||||
@@ -67,7 +65,9 @@ const logoLink = computed(() => {
|
||||
item.AlbumArtists[0],
|
||||
BaseItemKind.MusicArtist
|
||||
);
|
||||
} else if (item.Type === BaseItemKind.Episode && item.SeriesId) {
|
||||
}
|
||||
|
||||
if (item.Type === BaseItemKind.Episode && item.SeriesId) {
|
||||
return getItemDetailsLink({ Id: item.SeriesId }, BaseItemKind.Series);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -36,22 +36,24 @@ const error = shallowRef(false);
|
||||
const canvasRef = useTemplateRef('canvas');
|
||||
|
||||
watch(canvasRef, async () => {
|
||||
if (canvasRef.value) {
|
||||
error.value = false;
|
||||
if (!canvasRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const offscreen = canvasRef.value.transferControlToOffscreen();
|
||||
error.value = false;
|
||||
|
||||
await blurhashDrawer.draw(transfer(
|
||||
{ canvas: offscreen,
|
||||
hash: hash,
|
||||
width: width,
|
||||
height: height,
|
||||
punch: punch
|
||||
}, [offscreen]));
|
||||
} catch {
|
||||
error.value = true;
|
||||
}
|
||||
try {
|
||||
const offscreen = canvasRef.value.transferControlToOffscreen();
|
||||
|
||||
await blurhashDrawer.draw(transfer(
|
||||
{ canvas: offscreen,
|
||||
hash: hash,
|
||||
width: width,
|
||||
height: height,
|
||||
punch: punch
|
||||
}, [offscreen]));
|
||||
} catch {
|
||||
error.value = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -77,9 +77,7 @@ const id = useId();
|
||||
const root = isNil(inject(JView_isRouting));
|
||||
|
||||
const resolved = computed({
|
||||
get() {
|
||||
return _resolveStatus.value[id] ?? false;
|
||||
},
|
||||
get: () => _resolveStatus.value[id] ?? false,
|
||||
set(newVal) {
|
||||
_resolveStatus.value[id] = newVal;
|
||||
|
||||
|
||||
@@ -28,9 +28,7 @@ const currentInput = ref(0);
|
||||
const clicked = ref(false);
|
||||
const runtime = computed(() => playbackManager.currentItemRuntime.value / 1000);
|
||||
const sliderValue = computed({
|
||||
get() {
|
||||
return clicked.value ? currentInput.value : playbackManager.currentTime.value;
|
||||
},
|
||||
get: () => clicked.value ? currentInput.value : playbackManager.currentTime.value,
|
||||
set(newValue) {
|
||||
currentInput.value = newValue;
|
||||
}
|
||||
|
||||
@@ -55,10 +55,12 @@ const container = useTemplateRef('container');
|
||||
* Destroys the sortable instance
|
||||
*/
|
||||
function destroy(): void {
|
||||
if (sortable) {
|
||||
sortable.destroy();
|
||||
sortable = undefined;
|
||||
if (!sortable) {
|
||||
return;
|
||||
}
|
||||
|
||||
sortable.destroy();
|
||||
sortable = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,10 +14,12 @@ const visualizerElement = useTemplateRef('visualizerElement');
|
||||
* Destroy the visualizer instance.
|
||||
*/
|
||||
function destroy(): void {
|
||||
if (visualizerInstance) {
|
||||
visualizerInstance.destroy();
|
||||
visualizerInstance = undefined;
|
||||
if (!visualizerInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
visualizerInstance.destroy();
|
||||
visualizerInstance = undefined;
|
||||
}
|
||||
|
||||
watch([visualizerElement, mediaWebAudio.sourceNode], () => {
|
||||
|
||||
@@ -63,7 +63,9 @@ const hls = Hls.isSupported()
|
||||
const mediaElementType = computed<'audio' | 'video' | undefined>(() => {
|
||||
if (playbackManager.isAudio.value) {
|
||||
return 'audio';
|
||||
} else if (playbackManager.isVideo.value) {
|
||||
}
|
||||
|
||||
if (playbackManager.isVideo.value) {
|
||||
return 'video';
|
||||
}
|
||||
});
|
||||
@@ -81,10 +83,12 @@ const posterUrl = computed(() =>
|
||||
* Detaches HLS instance after playback is done
|
||||
*/
|
||||
function detachHls(): void {
|
||||
if (hls) {
|
||||
hls.detachMedia();
|
||||
hls.off(Events.ERROR, onHlsEror);
|
||||
if (!hls) {
|
||||
return;
|
||||
}
|
||||
|
||||
hls.detachMedia();
|
||||
hls.off(Events.ERROR, onHlsEror);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,16 +124,18 @@ async function attachWebAudio(el: HTMLMediaElement): Promise<void> {
|
||||
* Called by the media element when the playback is ready
|
||||
*/
|
||||
async function onLoadedData(): Promise<void> {
|
||||
if (playbackManager.isVideo.value) {
|
||||
if (mediaElementRef.value) {
|
||||
/**
|
||||
* Makes the resume start from the correct time
|
||||
*/
|
||||
mediaElementRef.value.currentTime = playbackManager.currentTime.value;
|
||||
}
|
||||
|
||||
await playerElement.applyCurrentSubtitle();
|
||||
if (!playbackManager.isVideo.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mediaElementRef.value) {
|
||||
/**
|
||||
* Makes the resume start from the correct time
|
||||
*/
|
||||
mediaElementRef.value.currentTime = playbackManager.currentTime.value;
|
||||
}
|
||||
|
||||
await playerElement.applyCurrentSubtitle();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,7 @@ const findSubtitle = (dialogue: ParsedSubtitleTrack['dialogue'], start = 0) => {
|
||||
};
|
||||
|
||||
const dialogue = computed(() => playerElement.currentExternalSubtitleTrack.value?.parsed?.dialogue);
|
||||
const currentSubtitle = computed<{ index: number; sub?: Dialogue } | undefined>((previous) => {
|
||||
const currentSubtitle = computed<undefined | { index: number; sub?: Dialogue }>((previous) => {
|
||||
if (!isNil(dialogue.value)) {
|
||||
const hasPrevious = !isNil(previous);
|
||||
const nextIndex = hasPrevious ? previous.index + 1 : 0;
|
||||
@@ -60,7 +60,9 @@ const currentSubtitle = computed<{ index: number; sub?: Dialogue } | undefined>(
|
||||
|
||||
if (!isNil(newIndex)) {
|
||||
return { index: newIndex, sub: dialogue.value[newIndex] };
|
||||
} else if (hasPrevious) {
|
||||
}
|
||||
|
||||
if (hasPrevious) {
|
||||
return { index: previous.index };
|
||||
}
|
||||
}
|
||||
@@ -70,9 +72,13 @@ const currentSubtitle = computed<{ index: number; sub?: Dialogue } | undefined>(
|
||||
const fontFamily = computed(() => {
|
||||
if (subtitleSettings.state.value.fontFamily === 'default') {
|
||||
return DEFAULT_TYPOGRAPHY;
|
||||
} else if (subtitleSettings.state.value.fontFamily === 'system') {
|
||||
}
|
||||
|
||||
if (subtitleSettings.state.value.fontFamily === 'system') {
|
||||
return 'system-ui';
|
||||
} else if (subtitleSettings.state.value.fontFamily !== 'auto') {
|
||||
}
|
||||
|
||||
if (subtitleSettings.state.value.fontFamily !== 'auto') {
|
||||
return subtitleSettings.state.value.fontFamily;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -92,9 +92,13 @@ const nextUpDuration = computed(() => {
|
||||
*/
|
||||
if (currentItemDuration.value >= 5 * 60 * 60) {
|
||||
return 540;
|
||||
} else if (currentItemDuration.value >= 2 * 60 * 60) {
|
||||
}
|
||||
|
||||
if (currentItemDuration.value >= 2 * 60 * 60) {
|
||||
return 210;
|
||||
} else if (currentItemDuration.value >= 45 * 60) {
|
||||
}
|
||||
|
||||
if (currentItemDuration.value >= 45 * 60) {
|
||||
return 120;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ const { query: permissionQuery, isSupported, state: fontPermission } = usePermis
|
||||
const fontAccess = computed(() => fontPermission.value === 'granted');
|
||||
const isQueryLocalFontsSupported = useSupported(() => isSupported.value && 'queryLocalFonts' in globalThis);
|
||||
const askForPermission = async () => isQueryLocalFontsSupported.value
|
||||
? Promise.all([permissionQuery, globalThis.queryLocalFonts])
|
||||
? Promise.all([permissionQuery, queryLocalFonts])
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,14 +33,14 @@ export function useSnackbar(message: string, color: string): void {
|
||||
|
||||
<script setup lang="ts">
|
||||
const model = computed({
|
||||
get() {
|
||||
return state.message !== '';
|
||||
},
|
||||
get: () => state.message !== '',
|
||||
set(newValue) {
|
||||
if (!newValue) {
|
||||
state.message = '';
|
||||
state.color = '';
|
||||
if (newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.message = '';
|
||||
state.color = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -378,9 +378,9 @@ function _sharedInternalLogic<T extends Record<K, (...args: any[]) => any>, K ex
|
||||
return ofBaseItem && !ops.skipCache.baseItem
|
||||
? cachedItems?.ref.value ?? previous ?? fetchResult.value
|
||||
: fetchResult.value;
|
||||
} else {
|
||||
return cachedData.value;
|
||||
}
|
||||
|
||||
return cachedData.value;
|
||||
});
|
||||
const isCached = computed(() =>
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
|
||||
@@ -367,9 +367,7 @@ const currentSourceIndex = computed(() =>
|
||||
);
|
||||
|
||||
const currentSource = computed({
|
||||
get() {
|
||||
return selectedSource.value ?? item.value.MediaSources?.[0] ?? {};
|
||||
},
|
||||
get: () => selectedSource.value ?? item.value.MediaSources?.[0] ?? {},
|
||||
set(newValue) {
|
||||
selectedSource.value = newValue;
|
||||
}
|
||||
|
||||
@@ -131,9 +131,9 @@ const viewType = computed({
|
||||
get() {
|
||||
if (innerItemKind.value) {
|
||||
return innerItemKind.value;
|
||||
} else {
|
||||
return library.value.CollectionType ? COLLECTION_TYPES_MAPPINGS[library.value.CollectionType] : undefined;
|
||||
}
|
||||
|
||||
return library.value.CollectionType ? COLLECTION_TYPES_MAPPINGS[library.value.CollectionType] : undefined;
|
||||
},
|
||||
set(newVal) {
|
||||
innerItemKind.value = newVal;
|
||||
|
||||
@@ -194,10 +194,12 @@ async function changePassword() {
|
||||
}
|
||||
|
||||
watch(selectedUserPicture, async (newVal) => {
|
||||
if (newVal) {
|
||||
await nextTick();
|
||||
await changeUserImage();
|
||||
if (!newVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
await changeUserImage();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -256,19 +256,21 @@ class RemotePluginAuth extends BaseState<AuthState> {
|
||||
* @param skipRequest - Skips the request and directly removes the user from the store
|
||||
*/
|
||||
public readonly logoutCurrentUser = async (skipRequest = false): Promise<void> => {
|
||||
if (!isNil(this.currentUser.value) && !isNil(this.currentServer.value)) {
|
||||
await this._runCallbacks(this._callbacks.beforeLogout);
|
||||
await this.logoutUser(this.currentUser.value, this.currentServer.value, skipRequest);
|
||||
|
||||
this._state.value.currentUserIndex = -1;
|
||||
/**
|
||||
* We need this so the callbacks are run after all the dependencies are updated
|
||||
* (i.e the page component is routed to index).
|
||||
*/
|
||||
globalThis.requestAnimationFrame(() =>
|
||||
globalThis.setTimeout(() => void this._runCallbacks(this._callbacks.afterLogout))
|
||||
);
|
||||
if (!(!isNil(this.currentUser.value) && !isNil(this.currentServer.value))) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this._runCallbacks(this._callbacks.beforeLogout);
|
||||
await this.logoutUser(this.currentUser.value, this.currentServer.value, skipRequest);
|
||||
|
||||
this._state.value.currentUserIndex = -1;
|
||||
/**
|
||||
* We need this so the callbacks are run after all the dependencies are updated
|
||||
* (i.e the page component is routed to index).
|
||||
*/
|
||||
globalThis.requestAnimationFrame(() =>
|
||||
globalThis.setTimeout(() => void this._runCallbacks(this._callbacks.afterLogout), 0)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,12 +18,12 @@ import { version } from '#/package.json';
|
||||
*/
|
||||
function ensureDeviceId(): string {
|
||||
const storageKey = 'deviceId';
|
||||
const val = globalThis.localStorage.getItem(storageKey);
|
||||
const val = localStorage.getItem(storageKey);
|
||||
|
||||
if (!val) {
|
||||
const id = v4();
|
||||
|
||||
globalThis.localStorage.setItem(storageKey, id);
|
||||
localStorage.setItem(storageKey, id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ import { remote } from '#/plugins/remote/index.ts';
|
||||
export function adminGuard(
|
||||
to: RouteLocationNormalized
|
||||
): NavigationGuardReturn {
|
||||
if (to.meta.admin && !remote.auth.currentUser.value?.Policy?.IsAdministrator) {
|
||||
useSnackbar(i18next.t('unauthorized'), 'error');
|
||||
|
||||
return false;
|
||||
if (!(to.meta.admin && !remote.auth.currentUser.value?.Policy?.IsAdministrator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
useSnackbar(i18next.t('unauthorized'), 'error');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,13 @@ async function _getBestServerPage(): Promise<Nullish<keyof RouteNamedMap>> {
|
||||
|
||||
if (!remote.auth.addedServers.value) {
|
||||
return serverAddUrl;
|
||||
} else if (isNil(remote.auth.currentServer.value)) {
|
||||
}
|
||||
|
||||
if (isNil(remote.auth.currentServer.value)) {
|
||||
return serverSelectUrl;
|
||||
} else if (!remote.auth.currentServer.value.StartupWizardCompleted) {
|
||||
}
|
||||
|
||||
if (!remote.auth.currentServer.value.StartupWizardCompleted) {
|
||||
return serverWizard;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import { useSnackbar } from '#/composables/use-snackbar.ts';
|
||||
* Validates that no playback is happening when accesing a route
|
||||
*/
|
||||
export function playbackGuard(): NavigationGuardReturn {
|
||||
if (isNil(playbackManager.currentItem.value)) {
|
||||
useSnackbar(i18next.t('routeValidationError'), 'error');
|
||||
|
||||
return false;
|
||||
if (!isNil(playbackManager.currentItem.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
useSnackbar(i18next.t('routeValidationError'), 'error');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -36,15 +36,17 @@ class ApiDatabase extends BaseDb {
|
||||
await this.items.where('Id').anyOf(itemIds).primaryKeys();
|
||||
|
||||
private readonly _getRequest = async (cache?: ApiResponse) => {
|
||||
if (cache) {
|
||||
if (cache.ofBaseItem) {
|
||||
const array = await this.getItemsById(cache.ids);
|
||||
|
||||
return cache.wasArray ? array : array[0];
|
||||
}
|
||||
|
||||
return cache.rawResult;
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cache.ofBaseItem) {
|
||||
const array = await this.getItemsById(cache.ids);
|
||||
|
||||
return cache.wasArray ? array : array[0];
|
||||
}
|
||||
|
||||
return cache.rawResult;
|
||||
};
|
||||
|
||||
public readonly getCachedRequest = async (funcName: string, params: string) =>
|
||||
|
||||
@@ -129,10 +129,12 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
||||
public readonly currentItemIndex = computed({
|
||||
get: () => this._state.value.currentItemIndex,
|
||||
set: (newIndex: number | undefined) => {
|
||||
if (newIndex !== this._state.value.currentItemIndex) {
|
||||
this._state.value.currentItemIndex = newIndex;
|
||||
this.currentTime.value = 0;
|
||||
if (newIndex === this._state.value.currentItemIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state.value.currentItemIndex = newIndex;
|
||||
this.currentTime.value = 0;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -256,7 +258,9 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
||||
private readonly _previousItemIndex = computed(() => {
|
||||
if (this.isRepeatingAll.value && this._state.value.currentItemIndex === 0) {
|
||||
return this.queueLength.value - 1;
|
||||
} else if (!isNil(this._state.value.currentItemIndex)) {
|
||||
}
|
||||
|
||||
if (!isNil(this._state.value.currentItemIndex)) {
|
||||
return this._state.value.currentItemIndex - 1;
|
||||
}
|
||||
});
|
||||
@@ -266,7 +270,9 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
||||
private readonly _nextItemIndex = computed(() => {
|
||||
if (this.isRepeatingAll.value && this._state.value.currentItemIndex === this.queueLength.value - 1) {
|
||||
return 0;
|
||||
} else if (!isNil(this._state.value.currentItemIndex)) {
|
||||
}
|
||||
|
||||
if (!isNil(this._state.value.currentItemIndex)) {
|
||||
return this._state.value.currentItemIndex + 1;
|
||||
}
|
||||
});
|
||||
@@ -743,7 +749,9 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
||||
}
|
||||
|
||||
return `${remote.sdk.api.basePath}/${mediaType}/${mediaSource.Id}/stream.${mediaSource.Container}?${parameters}`;
|
||||
} else if (remote.sdk.api?.basePath && mediaSource?.SupportsTranscoding && mediaSource.TranscodingUrl) {
|
||||
}
|
||||
|
||||
if (remote.sdk.api?.basePath && mediaSource?.SupportsTranscoding && mediaSource.TranscodingUrl) {
|
||||
return `${remote.sdk.api.basePath}${mediaSource.TranscodingUrl}`;
|
||||
}
|
||||
};
|
||||
@@ -809,7 +817,7 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
||||
watchEffect(() => {
|
||||
const { t } = i18next;
|
||||
|
||||
globalThis.navigator.mediaSession.metadata = this.currentItem.value
|
||||
navigator.mediaSession.metadata = this.currentItem.value
|
||||
? new MediaMetadata({
|
||||
title: this.currentItem.value.Name ?? t('unknownTitle'),
|
||||
artist: this.currentItem.value.AlbumArtist ?? t('unknownArtist'),
|
||||
@@ -828,16 +836,16 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
||||
watchEffect(() => {
|
||||
switch (this.status.value) {
|
||||
case PlaybackStatus.Playing: {
|
||||
globalThis.navigator.mediaSession.playbackState = 'playing';
|
||||
navigator.mediaSession.playbackState = 'playing';
|
||||
break;
|
||||
}
|
||||
case PlaybackStatus.Paused:
|
||||
case PlaybackStatus.Buffering: {
|
||||
globalThis.navigator.mediaSession.playbackState = 'paused';
|
||||
navigator.mediaSession.playbackState = 'paused';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
globalThis.navigator.mediaSession.playbackState = 'none';
|
||||
navigator.mediaSession.playbackState = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -866,7 +874,7 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
||||
|
||||
for (const action in actionHandlers) {
|
||||
try {
|
||||
globalThis.navigator.mediaSession.setActionHandler(
|
||||
navigator.mediaSession.setActionHandler(
|
||||
action as MediaSessionAction,
|
||||
/* eslint-disable-next-line unicorn/no-null */
|
||||
add ? actionHandlers[action as keyof typeof actionHandlers] ?? null : null
|
||||
@@ -890,7 +898,7 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
||||
if (this.currentTime.value <= this.currentItemRuntime.value) {
|
||||
const duration = this.currentItemRuntime.value / 1000;
|
||||
|
||||
globalThis.navigator.mediaSession.setPositionState(
|
||||
navigator.mediaSession.setPositionState(
|
||||
remove
|
||||
? undefined
|
||||
: {
|
||||
|
||||
@@ -26,7 +26,7 @@ class ClientSettingsStore extends SyncedStore<ClientSettingsState, KeysOfUnion<C
|
||||
/**
|
||||
* Removes the culture info from the language string, so 'es-ES' is recognised as 'es'
|
||||
*/
|
||||
this._navigatorLanguage.language.value?.split('-')[0]
|
||||
this._navigatorLanguage.language.value?.split('-', 1)[0]
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,11 +56,9 @@ class ThemeSettingsStore extends SyncedStore<ThemeSettingsState, 'typography'> {
|
||||
public readonly currentTypography = computed(() => {
|
||||
if (this._state.value.typography === 'system') {
|
||||
return 'system-ui';
|
||||
} else if (this._state.value.typography === 'default') {
|
||||
return DEFAULT_TYPOGRAPHY;
|
||||
} else {
|
||||
return this._state.value.typography;
|
||||
}
|
||||
|
||||
return this._state.value.typography === 'default' ? DEFAULT_TYPOGRAPHY : this._state.value.typography;
|
||||
});
|
||||
|
||||
public constructor() {
|
||||
|
||||
@@ -21,7 +21,7 @@ export function supportsMediaSource(): boolean {
|
||||
* Browsers that lack a media source implementation will have no reference
|
||||
* to |window.MediaSource|.
|
||||
*/
|
||||
return !!globalThis.MediaSource;
|
||||
return !!MediaSource;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -105,7 +105,9 @@ export function getImageTag(
|
||||
|
||||
if (item.ImageTags?.[type]) {
|
||||
return item.ImageTags[type];
|
||||
} else if (type === ImageType.Backdrop && item.BackdropImageTags?.[index]) {
|
||||
}
|
||||
|
||||
if (type === ImageType.Backdrop && item.BackdropImageTags?.[index]) {
|
||||
return item.BackdropImageTags[index];
|
||||
}
|
||||
|
||||
@@ -144,23 +146,41 @@ export function getImageTag(
|
||||
export function getParentId(item: BaseItemDto): string | undefined {
|
||||
if (item.AlbumId) {
|
||||
return item.AlbumId;
|
||||
} else if (item.ChannelId) {
|
||||
}
|
||||
|
||||
if (item.ChannelId) {
|
||||
return item.ChannelId;
|
||||
} else if (item.SeriesId) {
|
||||
}
|
||||
|
||||
if (item.SeriesId) {
|
||||
return item.SeriesId;
|
||||
} else if (item.ParentArtItemId) {
|
||||
}
|
||||
|
||||
if (item.ParentArtItemId) {
|
||||
return item.ParentArtItemId;
|
||||
} else if (item.ParentPrimaryImageItemId) {
|
||||
}
|
||||
|
||||
if (item.ParentPrimaryImageItemId) {
|
||||
return item.ParentPrimaryImageItemId;
|
||||
} else if (item.ParentThumbItemId) {
|
||||
}
|
||||
|
||||
if (item.ParentThumbItemId) {
|
||||
return item.ParentThumbItemId;
|
||||
} else if (item.ParentBackdropItemId) {
|
||||
}
|
||||
|
||||
if (item.ParentBackdropItemId) {
|
||||
return item.ParentBackdropItemId;
|
||||
} else if (item.ParentLogoItemId) {
|
||||
}
|
||||
|
||||
if (item.ParentLogoItemId) {
|
||||
return item.ParentLogoItemId;
|
||||
} else if (item.SeasonId) {
|
||||
}
|
||||
|
||||
if (item.SeasonId) {
|
||||
return item.SeasonId;
|
||||
} else if (item.ParentId) {
|
||||
}
|
||||
|
||||
if (item.ParentId) {
|
||||
return item.ParentId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,21 @@ import { isApple, isTizen, isTv, isWebOS } from '#/utils/browser-detection.ts';
|
||||
* Determines if audio codec is supported
|
||||
*/
|
||||
export function getSupportedAudioCodecs(format: string): boolean {
|
||||
let typeString;
|
||||
|
||||
if (format === 'flac' && isTv()) {
|
||||
return true;
|
||||
} else if (format === 'wma' && isTizen()) {
|
||||
}
|
||||
|
||||
if (format === 'wma' && isTizen()) {
|
||||
return true;
|
||||
} else if (format === 'asf' && isTv()) {
|
||||
}
|
||||
|
||||
if (format === 'asf' && isTv()) {
|
||||
return true;
|
||||
} else if (format === 'opus') {
|
||||
}
|
||||
|
||||
let typeString;
|
||||
|
||||
if (format === 'opus') {
|
||||
if (!isWebOS()) {
|
||||
typeString = 'audio/ogg; codecs="opus"';
|
||||
|
||||
@@ -27,9 +33,13 @@ export function getSupportedAudioCodecs(format: string): boolean {
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (format === 'alac' && isApple()) {
|
||||
}
|
||||
|
||||
if (format === 'alac' && isApple()) {
|
||||
return true;
|
||||
} else if (format === 'webma') {
|
||||
}
|
||||
|
||||
if (format === 'webma') {
|
||||
typeString = 'audio/webm';
|
||||
} else if (format === 'mp2') {
|
||||
typeString = 'audio/mpeg';
|
||||
|
||||
@@ -89,7 +89,7 @@ export function hasHevcSupport(videoTestElement: HTMLVideoElement): boolean {
|
||||
export function hasAv1Support(videoTestElement: HTMLVideoElement): boolean {
|
||||
if (
|
||||
(isTizen() && isTizen55())
|
||||
|| (isWebOS5() && globalThis.outerHeight >= 2160)
|
||||
|| (isWebOS5() && outerHeight >= 2160)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user