diff --git a/.github/renovate.json b/.github/renovate.json index 9bfb15c5e3..200d5e43e7 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -2,7 +2,14 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "github>jellyfin/.github//renovate-presets/nodejs", - ":semanticCommitsDisabled", ":dependencyDashboard" + ], + "packageRules": [ + { + "matchPackageNames": [ "@jellyfin/sdk" ], + "followTag": "unstable", + "minimumReleaseAge": null, + "schedule": [ "after 7:00 am" ] + } ] } diff --git a/.github/workflows/update-sdk.yml b/.github/workflows/update-sdk.yml deleted file mode 100644 index c76d34729e..0000000000 --- a/.github/workflows/update-sdk.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Update the Jellyfin SDK - -on: - schedule: - - cron: '0 7 * * *' - workflow_dispatch: - -concurrency: - group: unstable-sdk-pr - cancel-in-progress: true - -jobs: - update: - runs-on: ubuntu-latest - if: ${{ github.repository == 'jellyfin/jellyfin-web' }} - - steps: - - name: Check out Git repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - ref: master - token: ${{ secrets.JF_BOT_TOKEN }} - - - name: Set up Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: 20 - check-latest: true - cache: npm - - - name: Install latest unstable SDK - run: | - npm i --save @jellyfin/sdk@unstable - VERSION=$(jq -r '.dependencies["@jellyfin/sdk"]' package.json) - echo "JF_SDK_VERSION=${VERSION}" >> $GITHUB_ENV - - - name: Open a pull request - uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 # v7.0.1 - with: - token: ${{ secrets.JF_BOT_TOKEN }} - commit-message: Update @jellyfin/sdk to ${{env.JF_SDK_VERSION}} - committer: jellyfin-bot - author: jellyfin-bot - branch: update-jf-sdk - delete-branch: true - title: Update @jellyfin/sdk to ${{env.JF_SDK_VERSION}} - body: | - **Changes** - Updates to the latest unstable @jellyfin/sdk build - labels: | - dependencies - npm diff --git a/package-lock.json b/package-lock.json index aa0e5591f3..355a5821d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@fontsource/noto-sans-sc": "5.1.0", "@fontsource/noto-sans-tc": "5.1.0", "@jellyfin/libass-wasm": "4.2.3", - "@jellyfin/sdk": "0.0.0-unstable.202409260501", + "@jellyfin/sdk": "0.0.0-unstable.202410020501", "@mui/icons-material": "5.16.7", "@mui/material": "5.16.7", "@mui/x-date-pickers": "7.18.0", @@ -5068,9 +5068,9 @@ "license": "LGPL-2.1-or-later AND (FTL OR GPL-2.0-or-later) AND MIT AND MIT-Modern-Variant AND ISC AND NTP AND Zlib AND BSL-1.0" }, "node_modules/@jellyfin/sdk": { - "version": "0.0.0-unstable.202409260501", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202409260501.tgz", - "integrity": "sha512-3W/J15A/jELTMJPrPiGvfkO8xsUaxKh4P/f+kVAxOSCYkPp+vvZxmRCGV7M1+1947O19iJ/l7ypD08L3YVf39g==", + "version": "0.0.0-unstable.202410020501", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202410020501.tgz", + "integrity": "sha512-Tl+bE4GoGDf5qmzYiauNLFclAYF4cPFVX3HldKl/KuwEarzRdQfudGNFCu0KmDbJGiZ6HtDlec6LoUaYeXmvng==", "license": "MPL-2.0", "peerDependencies": { "axios": "^1.3.4" @@ -29306,9 +29306,9 @@ "integrity": "sha512-C0OlBxIr9NdeFESMTA/OVDqNSWtog6Mi7wwzwH12xbZpxsMD0RgCupUcIP7zZgcpTNecW3fZq5d6xVo7Q8HEJw==" }, "@jellyfin/sdk": { - "version": "0.0.0-unstable.202409260501", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202409260501.tgz", - "integrity": "sha512-3W/J15A/jELTMJPrPiGvfkO8xsUaxKh4P/f+kVAxOSCYkPp+vvZxmRCGV7M1+1947O19iJ/l7ypD08L3YVf39g==", + "version": "0.0.0-unstable.202410020501", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202410020501.tgz", + "integrity": "sha512-Tl+bE4GoGDf5qmzYiauNLFclAYF4cPFVX3HldKl/KuwEarzRdQfudGNFCu0KmDbJGiZ6HtDlec6LoUaYeXmvng==", "requires": {} }, "@jridgewell/gen-mapping": { diff --git a/package.json b/package.json index 47063fda6f..fb62f4be96 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "@fontsource/noto-sans-sc": "5.1.0", "@fontsource/noto-sans-tc": "5.1.0", "@jellyfin/libass-wasm": "4.2.3", - "@jellyfin/sdk": "0.0.0-unstable.202409260501", + "@jellyfin/sdk": "0.0.0-unstable.202410020501", "@mui/icons-material": "5.16.7", "@mui/material": "5.16.7", "@mui/x-date-pickers": "7.18.0", diff --git a/src/apps/stable/features/playback/constants/playbackManagerEvent.ts b/src/apps/stable/features/playback/constants/playbackManagerEvent.ts new file mode 100644 index 0000000000..2200c833b5 --- /dev/null +++ b/src/apps/stable/features/playback/constants/playbackManagerEvent.ts @@ -0,0 +1,14 @@ +/** + * Events triggered by PlaybackManager. + */ +export enum PlaybackManagerEvent { + Pairing = 'pairing', + Paired = 'paired', + PairError = 'pairerror', + PlaybackCancelled = 'playbackcancelled', + PlaybackError = 'playbackerror', + PlaybackStart = 'playbackstart', + PlaybackStop = 'playbackstop', + PlayerChange = 'playerchange', + ReportPlayback = 'reportplayback' +} diff --git a/src/apps/stable/features/playback/constants/playerEvent.ts b/src/apps/stable/features/playback/constants/playerEvent.ts new file mode 100644 index 0000000000..c236475383 --- /dev/null +++ b/src/apps/stable/features/playback/constants/playerEvent.ts @@ -0,0 +1,23 @@ +/** + * Events triggered by media player plugins. + * TODO: This list is incomplete + */ +export enum PlayerEvent { + Error = 'error', + FullscreenChange = 'fullscreenchange', + ItemStarted = 'itemstarted', + ItemStopped = 'itemstopped', + MediaStreamsChange = 'mediastreamschange', + Pause = 'pause', + PlaybackStart = 'playbackstart', + PlaybackStop = 'playbackstop', + PlaylistItemAdd = 'playlistitemadd', + PlaylistItemMove = 'playlistitemmove', + PlaylistItemRemove = 'playlistitemremove', + RepeatModeChange = 'repeatmodechange', + ShuffleModeChange = 'shufflequeuemodechange', + Stopped = 'stopped', + TimeUpdate = 'timeupdate', + Unpause = 'unpause', + VolumeChange = 'volumechange' +} diff --git a/src/apps/stable/features/playback/types/callbacks.ts b/src/apps/stable/features/playback/types/callbacks.ts new file mode 100644 index 0000000000..1bede1b2c0 --- /dev/null +++ b/src/apps/stable/features/playback/types/callbacks.ts @@ -0,0 +1,33 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'; +import type { MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client/models/media-source-info'; +import type { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type'; + +import type { StreamInfo } from './streamInfo'; + +export interface ManagedPlayerStopInfo { + item: BaseItemDto + mediaSource: MediaSourceInfo + nextItem?: BaseItemDto | null + nextMediaType?: MediaType | null + positionMs?: number +} + +export interface MovedItem { + newIndex: number + playlistItemId: string +} + +export type PlayerErrorCode = string; + +export interface PlayerStopInfo { + src?: URL | BaseItemDto +} + +export interface PlayerError { + streamInfo?: StreamInfo + type: MediaError | string +} + +export interface RemovedItems { + playlistItemIds: string[] +} diff --git a/src/apps/stable/features/playback/types/streamInfo.ts b/src/apps/stable/features/playback/types/streamInfo.ts new file mode 100644 index 0000000000..0fe54210f3 --- /dev/null +++ b/src/apps/stable/features/playback/types/streamInfo.ts @@ -0,0 +1,34 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'; +import type { MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client/models/media-source-info'; +import type { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type'; +import type { PlayMethod } from '@jellyfin/sdk/lib/generated-client/models/play-method'; + +export interface StreamInfo { + ended?: boolean + fullscreen?: boolean + item?: BaseItemDto + lastMediaInfoQuery?: number + liveStreamId?: string + mediaSource?: MediaSourceInfo + mediaType?: MediaType + mimeType?: string + playMethod?: PlayMethod + playSessionId?: string + playbackStartTimeTicks?: number + playerStartPositionTicks?: number + resetSubtitleOffset?: boolean + started?: boolean + textTracks?: TrackInfo[] + title?: string + tracks?: TrackInfo[] + transcodingOffsetTicks?: number + url?: string +} + +interface TrackInfo { + url: string + language: string + isDefault: boolean + index: number + format: string +} diff --git a/src/apps/stable/features/playback/utils/playbackSubscriber.ts b/src/apps/stable/features/playback/utils/playbackSubscriber.ts new file mode 100644 index 0000000000..dfc79360a2 --- /dev/null +++ b/src/apps/stable/features/playback/utils/playbackSubscriber.ts @@ -0,0 +1,101 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'; +import type { MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client/models/media-source-info'; + +import type { PlaybackManager } from 'components/playback/playbackmanager'; +import type { MediaError } from 'types/mediaError'; +import type { PlayTarget } from 'types/playTarget'; +import type { PlaybackStopInfo, PlayerState } from 'types/playbackStopInfo'; +import type { Plugin } from 'types/plugin'; +import Events, { type Event } from 'utils/events'; + +import { PlaybackManagerEvent } from '../constants/playbackManagerEvent'; +import { PlayerEvent } from '../constants/playerEvent'; +import type { ManagedPlayerStopInfo, MovedItem, PlayerError, PlayerErrorCode, PlayerStopInfo, RemovedItems } from '../types/callbacks'; + +export interface PlaybackSubscriber { + onPlaybackCancelled?(e: Event): void + onPlaybackError?(e: Event, errorType: MediaError): void + onPlaybackStart?(e: Event, player: Plugin, state: PlayerState): void + onPlaybackStop?(e: Event, info: PlaybackStopInfo): void + onPlayerChange?(e: Event, player: Plugin, target: PlayTarget, previousPlayer: Plugin): void + onPlayerError?(e: Event, error: PlayerError): void + onPlayerFullscreenChange?(e: Event): void + onPlayerItemStarted?(e: Event, item?: BaseItemDto, mediaSource?: MediaSourceInfo): void + onPlayerItemStopped?(e: Event, info: ManagedPlayerStopInfo): void + onPlayerMediaStreamsChange?(e: Event): void + onPlayerPause?(e: Event): void + onPlayerPlaybackStart?(e: Event, state: PlayerState): void + onPlayerPlaybackStop?(e: Event, state: PlayerState): void + onPlayerPlaylistItemAdd?(e: Event): void + onPlayerPlaylistItemMove?(e: Event, item: MovedItem): void + onPlayerPlaylistItemRemove?(e: Event, items?: RemovedItems): void + onPlayerRepeatModeChange?(e: Event): void + onPlayerShuffleModeChange?(e: Event): void + onPlayerStopped?(e: Event, info?: PlayerStopInfo | PlayerErrorCode): void + onPlayerTimeUpdate?(e: Event): void + onPlayerUnpause?(e: Event): void + onPlayerVolumeChange?(e: Event): void + onReportPlayback?(e: Event, isServerItem: boolean): void +} + +export abstract class PlaybackSubscriber { + private player: Plugin | undefined; + + private readonly playbackManagerEvents = { + [PlaybackManagerEvent.PlaybackCancelled]: this.onPlaybackCancelled, + [PlaybackManagerEvent.PlaybackError]: this.onPlaybackError, + [PlaybackManagerEvent.PlaybackStart]: this.onPlaybackStart, + [PlaybackManagerEvent.PlaybackStop]: this.onPlaybackStop, + [PlaybackManagerEvent.PlayerChange]: this.onPlayerChange, + [PlaybackManagerEvent.ReportPlayback]: this.onReportPlayback + }; + + private readonly playerEvents = { + [PlayerEvent.Error]: this.onPlayerError, + [PlayerEvent.FullscreenChange]: this.onPlayerFullscreenChange, + [PlayerEvent.ItemStarted]: this.onPlayerItemStarted, + [PlayerEvent.ItemStopped]: this.onPlayerItemStopped, + [PlayerEvent.MediaStreamsChange]: this.onPlayerMediaStreamsChange, + [PlayerEvent.Pause]: this.onPlayerPause, + [PlayerEvent.PlaybackStart]: this.onPlayerPlaybackStart, + [PlayerEvent.PlaybackStop]: this.onPlayerPlaybackStop, + [PlayerEvent.PlaylistItemAdd]: this.onPlayerPlaylistItemAdd, + [PlayerEvent.PlaylistItemMove]: this.onPlayerPlaylistItemMove, + [PlayerEvent.PlaylistItemRemove]: this.onPlayerPlaylistItemRemove, + [PlayerEvent.RepeatModeChange]: this.onPlayerRepeatModeChange, + [PlayerEvent.ShuffleModeChange]: this.onPlayerShuffleModeChange, + [PlayerEvent.Stopped]: this.onPlayerStopped, + [PlayerEvent.TimeUpdate]: this.onPlayerTimeUpdate, + [PlayerEvent.Unpause]: this.onPlayerUnpause, + [PlayerEvent.VolumeChange]: this.onPlayerVolumeChange + }; + + constructor( + protected readonly playbackManager: PlaybackManager + ) { + Object.entries(this.playbackManagerEvents).forEach(([event, handler]) => { + if (handler) Events.on(playbackManager, event, handler); + }); + + this.bindPlayerEvents(); + Events.on(playbackManager, PlaybackManagerEvent.PlayerChange, this.bindPlayerEvents.bind(this)); + } + + private bindPlayerEvents() { + const newPlayer = this.playbackManager.getCurrentPlayer(); + if (this.player === newPlayer) return; + + if (this.player) { + Object.entries(this.playerEvents).forEach(([event, handler]) => { + if (handler) Events.off(this.player, event, handler); + }); + } + + this.player = newPlayer; + if (!this.player) return; + + Object.entries(this.playerEvents).forEach(([event, handler]) => { + if (handler) Events.on(this.player, event, handler); + }); + } +} diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index b51046d340..e90aff609f 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -687,7 +687,7 @@ function sortPlayerTargets(a, b) { return aVal.localeCompare(bVal); } -class PlaybackManager { +export class PlaybackManager { constructor() { const self = this; diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 067d94c48c..029cdeca82 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -121,6 +121,14 @@ class HtmlAudioPlayer { normalizationGain = options.mediaSource.albumNormalizationGain ?? options.item.NormalizationGain; + } else { + console.debug('normalization disabled'); + return; + } + + if (!self.gainNode) { + addGainElement(elem); + if (!self.gainNode) return; } if (normalizationGain) { @@ -276,8 +284,6 @@ class HtmlAudioPlayer { self._mediaElement = elem; - addGainElement(elem); - return elem; } @@ -317,7 +323,7 @@ class HtmlAudioPlayer { function onVolumeChange() { if (!self._isFadingOut) { htmlMediaHelper.saveVolume(this.volume); - if (browser.safari) { + if (browser.safari && self.gainNode) { self.gainNode.gain.value = this.volume * self.normalizationGain; } Events.trigger(self, 'volumechange'); diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index 92fa5b43a8..f54f367915 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -1963,5 +1963,9 @@ "PreferNonstandardArtistsTag": "Prefer ARTISTS tag if available", "PreferNonstandardArtistsTagHelp": "Use the non-standard ARTISTS tag instead of ARTIST tag when available.", "UseCustomTagDelimiters": "Use custom tag delimiter", - "UseCustomTagDelimitersHelp": "Split artist/genre tags with custom characters." + "UseCustomTagDelimitersHelp": "Split artist/genre tags with custom characters.", + "DateModified": "Date modified", + "MessageCancelSeriesTimerError": "An error occurred while cancelling the series timer", + "MessageCancelTimerError": "An error occurred while cancelling the timer", + "MessageSplitVersionsError": "An error occurred while splitting versions" } diff --git a/src/strings/en-us.json b/src/strings/en-us.json index d2e32a1ff4..5cd78810ef 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -156,7 +156,7 @@ "ChannelNumber": "Channel number", "Channels": "Channels", "CinemaModeConfigurationHelp": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.", - "SelectAudioNormalizationHelp": "Track gain - adjusts the volume of each track so they playback with the same loudness. Album gain - adjusts the volume of all the tracks in an album only, keeping the album's dynamic range.", + "SelectAudioNormalizationHelp": "Track gain - adjusts the volume of each track so they playback with the same loudness. Album gain - adjusts the volume of all the tracks in an album only, keeping the album's dynamic range. Switching between \"Off\" and other options requires restarting the current playback.", "ClearQueue": "Clear queue", "ClientSettings": "Client Settings", "Collections": "Collections", diff --git a/src/strings/fr.json b/src/strings/fr.json index 5d651781b9..7187a3c4c8 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1267,7 +1267,7 @@ "OnWakeFromSleep": "À la sortie de veille", "WeeklyAt": "{0} à {1}", "DailyAt": "Tous les jours à {0}", - "LastSeen": "Vu {0}", + "LastSeen": "Dernière connexion {0}", "PersonRole": "est {0}", "ListPaging": "{0}-{1} de {2}", "WriteAccessRequired": "Jellyfin a besoin d'un accès en écriture à ce dossier. Merci de vérifier les permissions de ce-dernier puis de réessayer.", @@ -1930,5 +1930,41 @@ "EnableHi10p": "Active le profil H.264 High 10", "EnableHi10pHelp": "Activer pour éviter l'encodage des vidéos H.264 10-bits. Désactiver cette option si la vidéo affiche des images vides.", "AlwaysRemuxFlacAudioFilesHelp": "Si votre navigateur refuse de lire des fichiers ou s'il calcule incorrectement l'horodatage, activer ceci en guise d'alternative.", - "AlwaysRemuxMp3AudioFilesHelp": "Si votre navigateur calcule incorrectement l'horodatage de certains fichiers, activer ceci en guise d'alternative." + "AlwaysRemuxMp3AudioFilesHelp": "Si votre navigateur calcule incorrectement l'horodatage de certains fichiers, activer ceci en guise d'alternative.", + "DateModified": "Date modifiée", + "FallbackMaxStreamingBitrateHelp": "Le débit maximum de streaming est utilisé par défaut quand ffprobe ne peut pas déterminer le débit du flux source. Cela aide à éviter que les clients demandent un débit de transcodage excessivement élevé, ce qui pourrait mener le lecteur à échouer et surcharger l'encodeur.", + "LabelAlwaysRemuxFlacAudioFiles": "Toujours remultiplexer les fichiers audio FLAC", + "LabelAlwaysRemuxMp3AudioFiles": "Toujours remultiplexer les fichiers audio MP3", + "LabelAllowStreamSharing": "Autoriser le partage de flux", + "MessageCancelSeriesTimerError": "Une erreur est survenue lors de l'annulation du minuteur de série", + "AllowStreamSharingHelp": "Autoriser Jellyfin à dupliquer le flux mpegts provenant du tuner et à partager ce flux dupliqué avec ses clients. Cela est utile lorsque le tuner a une limite de nombre total de flux, mais peut également causer des problèmes de lecture.", + "Reset": "Réinitialiser", + "UseCustomTagDelimitersHelp": "Diviser les balises d'artiste/genre avec des caractères personnalisés.", + "HeaderAudioAdvanced": "Audio Avancé", + "LabelAudioTagSettings": "Paramètres des balises audio", + "LabelCustomTagDelimiters": "Délimiteur de balise personnalisé", + "LabelCustomTagDelimitersHelp": "Caractères à traiter comme délimiteurs pour séparer les balises.", + "LabelLyricDownloaders": "Téléchargeurs de Lyrics", + "LabelDelimiterWhitelist": "Liste blanche des délimiteurs", + "LabelDelimiterWhitelistHelp": "Éléments à exclure de la division des balises. Un élément par ligne.", + "LabelDisableVbrAudioEncoding": "Désactiver l'encodage audio VBR", + "LabelSaveTrickplayLocally": "Enregistrer les images trickplay au même emplacement que les médias", + "LabelSaveTrickplayLocallyHelp": "Enregistrer les images trickplay dans les dossiers de médias les placera à côté de vos médias pour une migration et un accès faciles.", + "MediaInfoRotation": "Rotation", + "RenderPgsSubtitle": "Rendu expérimental des sous-titres PGS", + "ReplaceTrickplayImages": "Remplacer les images trickplay existantes", + "LibraryInvalidItemIdError": "La bibliothèque est dans un état invalide et ne peut pas être modifiée. Vous rencontrez probablement un bug : le chemin dans la base de données n'est pas le bon chemin sur le système de fichiers.", + "MessageCancelTimerError": "Une erreur est survenue lors de l'annulation du minuteur", + "MessageSplitVersionsError": "Une erreur s'est produite lors de la division des versions", + "PreferNonstandardArtistsTag": "Préférer la balise ARTISTS si disponible", + "PreferNonstandardArtistsTagHelp": "Utiliser la balise non standard ARTISTS au lieu de la balise ARTIST lorsqu'elle est disponible.", + "RenderPgsSubtitleHelp": "Détermine si le client doit rendre les sous-titres PGS au lieu d'utiliser des sous-titres incrustés. Cela peut éviter le transcodage côté serveur au profit de la performance de rendu côté client.", + "UseCustomTagDelimiters": "Utiliser un délimiteur de balise personnalisé", + "VideoCodecTagNotSupported": "La balise de codec vidéo n'est pas prise en charge", + "LabelFallbackMaxStreamingBitrate": "Bitrate de flux maximum de repli (Mbps)", + "LyricDownloadersHelp": "Activez et classez vos téléchargeurs de sous-titres préférés par ordre de priorité.", + "LabelAllowFmp4TranscodingContainer": "Autoriser le conteneur de transcodage fMP4", + "AllowTonemappingSoftwareHelp": "Le tone-mapping peut transformer la plage dynamique d'une vidéo de HDR à SDR tout en conservant les détails et les couleurs de l'image, qui sont des informations très importantes pour représenter la scène originale. Fonctionne uniquement avec les vidéos 10 bits HDR10, HLG et DoVi.", + "LabelTrickplayKeyFrameOnlyExtraction": "Générer uniquement des images à partir des images clés", + "LabelTrickplayKeyFrameOnlyExtractionHelp": "Extraire uniquement les images clés pour un traitement beaucoup plus rapide avec un minutage moins précis. Si le décodeur matériel configuré ne prend pas en charge ce mode, il utilisera le décodeur logiciel à la place." } diff --git a/src/strings/nb.json b/src/strings/nb.json index d0e1dee15d..987f622dfe 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -1958,5 +1958,9 @@ "PreferNonstandardArtistsTag": "Foretrekk ARTISTER-tag om tilgjengelig", "PreferNonstandardArtistsTagHelp": "Bruk ikke-standard ARTISTS-taggen istedenfor ARTIST-taggen når den er tilgjengelig.", "UseCustomTagDelimiters": "Bruk egendefinert skilletegn for tagger", - "UseCustomTagDelimitersHelp": "Del artist- og sjanger-tagger med egendefinert tegn." + "UseCustomTagDelimitersHelp": "Del artist- og sjanger-tagger med egendefinert tegn.", + "DateModified": "Data modifisert", + "MessageCancelSeriesTimerError": "Det oppstod en feil under avbryting av serietidtakeren", + "MessageCancelTimerError": "Det oppstod en feil under avbryting av tidtakeren", + "MessageSplitVersionsError": "Det oppsto en feil under oppdeling av versjoner" } diff --git a/src/strings/nl.json b/src/strings/nl.json index cebcb545cd..6033a8b8c9 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1776,7 +1776,7 @@ "BackdropScreensaver": "Schermbeveiliging met achtergronden", "LogoScreensaver": "Schermbeveiliging met logo", "LabelIsHearingImpaired": "Voor slechthorenden (ODS)", - "SelectAudioNormalizationHelp": "Nummerversterking regelt het volume van elk individueel nummer zodat alle nummers even luid afspelen. Albumversterking regelt het volume van alle nummers op een album, zodat het dynamische bereik van het album behouden blijft.", + "SelectAudioNormalizationHelp": "Nummerversterking regelt het volume van elk individueel nummer zodat alle nummers even luid afspelen. Albumversterking regelt het volume van alle nummers op een album, waarbij het dynamische bereik van het album behouden blijft. Na het wisselen tussen deze opties moet het afspelen herstart worden.", "LabelAlbumGain": "Albumversterking", "LabelSelectAudioNormalization": "Geluidsnormalisatie", "LabelTrackGain": "Nummerversterking", diff --git a/src/strings/uk.json b/src/strings/uk.json index b34ef8960a..66f2dc6a5b 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1960,5 +1960,9 @@ "PreferNonstandardArtistsTag": "Надавати перевагу тегу ARTISTS, якщо він доступний", "PreferNonstandardArtistsTagHelp": "Використовувати нестандартний тег ARTISTS замість тега ARTIST, якщо він доступний.", "UseCustomTagDelimiters": "Використовувати власний роздільник тегів", - "UseCustomTagDelimitersHelp": "Розділіть теги виконавця/жанру за допомогою спеціальних символів." + "UseCustomTagDelimitersHelp": "Розділіть теги виконавця/жанру за допомогою спеціальних символів.", + "DateModified": "Дату змінено", + "MessageCancelSeriesTimerError": "Виникла помилка під час скасування таймера серіалу", + "MessageCancelTimerError": "Виникла помилка під час скасування таймера", + "MessageSplitVersionsError": "Виникла помилка під час розділення версій" } diff --git a/src/strings/vi.json b/src/strings/vi.json index ddd7bd5b73..5c8fb2a9b9 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -1960,5 +1960,9 @@ "ReplaceTrickplayImages": "Thay thế ảnh tua nhanh hiện có", "LabelAlwaysRemuxMp3AudioFiles": "Luôn làm lại các tập tin âm thanh MP3", "LabelSaveTrickplayLocally": "Lưu ảnh tua nhanh bên cạnh phương tiện", - "LabelSaveTrickplayLocallyHelp": "Việc lưu ảnh tua anh vào thư mục phương tiện sẽ đặt chúng kế bên phương tiện của bạn để dễ di chuyển và truy cập." + "LabelSaveTrickplayLocallyHelp": "Việc lưu ảnh tua anh vào thư mục phương tiện sẽ đặt chúng kế bên phương tiện của bạn để dễ di chuyển và truy cập.", + "DateModified": "Ngày sửa đổi", + "MessageCancelSeriesTimerError": "Đã xảy ra lỗi khi hủy bộ hẹn giờ chuỗi", + "MessageCancelTimerError": "Đã xảy ra lỗi khi hủy bộ hẹn giờ", + "MessageSplitVersionsError": "Đã xảy ra lỗi khi chia nhỏ các phiên bản" } diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 3969b117f4..f32fc318d6 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1019,7 +1019,7 @@ "SortName": "排序名称", "Sports": "体育", "StopRecording": "停止录制", - "Studios": "制片发行商", + "Studios": "工作室", "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "这些设置也会被应用于任何通过此设备发起的 Google Cast 播放。", "SubtitleAppearanceSettingsDisclaimer": "以下设置不适用于上述图形字幕或嵌入其自身样式的 ASS/SSA 字幕。", "SubtitleDownloadersHelp": "按优先顺序启用并排列您的首选字幕下载程序。", @@ -1728,7 +1728,7 @@ "MenuOpen": "打开菜单", "MenuClose": "关闭菜单", "UserMenu": "用户菜单", - "Studio": "制片发行商", + "Studio": "工作室", "AllowCollectionManagement": "允许该用户管理收藏夹", "EnableAudioNormalizationHelp": "音频标准化将添加一个恒定的增益,以保持平均音量在所需的级别(-18dB)。", "EnableAudioNormalization": "音频标准化", @@ -1779,7 +1779,7 @@ "LabelIsHearingImpaired": "用于听障/聋哑人士", "SearchResultsEmpty": "抱歉!未找到与\"{0}\"相关的结果", "LabelTrackGain": "音轨增益", - "SelectAudioNormalizationHelp": "音轨增益 - 调整每个音轨的音量,使它们播放时具有相同的响度。专辑增益 - 只调整专辑中所有音轨的音量,保持专辑的动态范围。", + "SelectAudioNormalizationHelp": "音轨增益 - 调整每个音轨的音量,使它们播放时具有相同的响度。专辑增益 - 只调整专辑中所有音轨的音量,保持专辑的动态范围。在“关闭”和其他选项之间切换后需要重新启动当前播放。", "LabelAlbumGain": "专辑增益", "LabelSelectAudioNormalization": "音频标准化", "HeaderAllRecordings": "所有录制的节目", diff --git a/src/types/playTarget.ts b/src/types/playTarget.ts index 33353137d5..b2f0f6a284 100644 --- a/src/types/playTarget.ts +++ b/src/types/playTarget.ts @@ -1,3 +1,4 @@ +import type { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type'; import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto'; export interface PlayTarget { @@ -7,5 +8,7 @@ export interface PlayTarget { playerName?: string deviceType?: string isLocalPlayer?: boolean + playableMediaTypes: MediaType[] + supportedCommands?: string[] user?: UserDto }