diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 12012783344b..1cbdf1d3a345 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -27,6 +27,7 @@ import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent import { bindMediaSegmentManager } from 'apps/stable/features/playback/utils/mediaSegmentManager'; import { bindMediaSessionSubscriber } from 'apps/stable/features/playback/utils/mediaSessionSubscriber'; import { AppFeature } from 'constants/appFeature'; +import { TICKS_PER_SECOND } from 'constants/time'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import { MediaError } from 'types/mediaError'; import { getMediaError } from 'utils/mediaError'; @@ -1345,6 +1346,10 @@ export class PlaybackManager { } }; + self.getFps = function (player) { + return self.currentMediaSource(player).MediaStreams.find(s => s.Type === 'Video')?.ReferenceFrameRate; + }; + function getSavedMaxStreamingBitrate(apiClient, mediaType) { if (!apiClient) { // This should hopefully never happen @@ -3884,6 +3889,20 @@ export class PlaybackManager { this.seekRelative(offsetTicks, player); } + nextFrame(player = this._currentPlayer) { + const fps = this.getFps(player); + if (fps) { + this.seekRelative(TICKS_PER_SECOND / fps, player); + } + } + + previousFrame(player = this._currentPlayer) { + const fps = this.getFps(player); + if (fps) { + this.seekRelative(-TICKS_PER_SECOND / fps, player); + } + } + seekPercent(percent, player = this._currentPlayer) { let ticks = this.duration(player) || 0; diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index ad959e2789e9..28b6b3af6daa 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1312,6 +1312,18 @@ export default function (view) { showOsd(btnFastForward); } break; + case ',': + if (!e.shiftKey) { + e.preventDefault(); + playbackManager.previousFrame(currentPlayer); + } + break; + case '.': + if (!e.shiftKey) { + e.preventDefault(); + playbackManager.nextFrame(currentPlayer); + } + break; case 'j': case 'J': case 'ArrowLeft':