@remotion/player 4.0.220 → 4.0.222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import type { AnyZodObject } from 'zod';
5
5
  import type { RenderMuteButton } from './MediaVolumeSlider.js';
6
6
  import type { RenderFullscreenButton, RenderPlayPauseButton } from './PlayerControls.js';
7
7
  import type { PosterFillMode, RenderLoading, RenderPoster } from './PlayerUI.js';
8
+ import type { BrowserMediaControlsBehavior } from './browser-mediasession.js';
8
9
  import type { PlayerRef } from './player-methods.js';
9
10
  import type { RenderVolumeSlider } from './render-volume-slider.js';
10
11
  import type { PropsIfHasProps } from './utils/props-if-has-props.js';
@@ -52,6 +53,7 @@ export type PlayerProps<Schema extends AnyZodObject, Props extends Record<string
52
53
  readonly bufferStateDelayInMilliseconds?: number;
53
54
  readonly hideControlsWhenPointerDoesntMove?: boolean | number;
54
55
  readonly overflowVisible?: boolean;
56
+ readonly browserMediaControlsBehavior?: BrowserMediaControlsBehavior;
55
57
  } & CompProps<Props> & PropsIfHasProps<Schema, Props>;
56
58
  export type PlayerPropsWithoutZod<Props extends Record<string, unknown>> = PlayerProps<AnyZodObject, Props>;
57
59
  export declare const componentOrNullIfLazy: <Props>(props: CompProps<Props>) => ComponentType<Props> | null;
@@ -22,7 +22,7 @@ const componentOrNullIfLazy = (props) => {
22
22
  return null;
23
23
  };
24
24
  exports.componentOrNullIfLazy = componentOrNullIfLazy;
25
- const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps, inputProps, style, controls = false, loop = false, autoPlay = false, showVolumeControls = true, allowFullscreen = true, clickToPlay, doubleClickToFullscreen = false, spaceKeyToPlayOrPause = true, moveToBeginningWhenEnded = true, numberOfSharedAudioTags = 5, errorFallback = () => '⚠️', playbackRate = 1, renderLoading, className, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, showPosterWhenBuffering, initialFrame, renderPoster, inFrame, outFrame, initiallyShowControls, renderFullscreenButton, renderPlayPauseButton, renderVolumeSlider, alwaysShowControls = false, initiallyMuted = false, showPlaybackRateControl = false, posterFillMode = 'player-size', bufferStateDelayInMilliseconds, hideControlsWhenPointerDoesntMove = true, overflowVisible = false, renderMuteButton, ...componentProps }, ref) => {
25
+ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps, inputProps, style, controls = false, loop = false, autoPlay = false, showVolumeControls = true, allowFullscreen = true, clickToPlay, doubleClickToFullscreen = false, spaceKeyToPlayOrPause = true, moveToBeginningWhenEnded = true, numberOfSharedAudioTags = 5, errorFallback = () => '⚠️', playbackRate = 1, renderLoading, className, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, showPosterWhenBuffering, initialFrame, renderPoster, inFrame, outFrame, initiallyShowControls, renderFullscreenButton, renderPlayPauseButton, renderVolumeSlider, alwaysShowControls = false, initiallyMuted = false, showPlaybackRateControl = false, posterFillMode = 'player-size', bufferStateDelayInMilliseconds, hideControlsWhenPointerDoesntMove = true, overflowVisible = false, renderMuteButton, browserMediaControlsBehavior: passedBrowserMediaControlsBehavior, ...componentProps }, ref) => {
26
26
  if (typeof window !== 'undefined') {
27
27
  // eslint-disable-next-line react-hooks/rules-of-hooks
28
28
  (0, react_1.useLayoutEffect)(() => {
@@ -138,9 +138,14 @@ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps,
138
138
  }, []);
139
139
  }
140
140
  const actualInputProps = (0, react_1.useMemo)(() => inputProps !== null && inputProps !== void 0 ? inputProps : {}, [inputProps]);
141
+ const browserMediaControlsBehavior = (0, react_1.useMemo)(() => {
142
+ return (passedBrowserMediaControlsBehavior !== null && passedBrowserMediaControlsBehavior !== void 0 ? passedBrowserMediaControlsBehavior : {
143
+ mode: 'prevent-media-session',
144
+ });
145
+ }, [passedBrowserMediaControlsBehavior]);
141
146
  return ((0, jsx_runtime_1.jsx)(remotion_1.Internals.IsPlayerContextProvider, { children: (0, jsx_runtime_1.jsx)(SharedPlayerContext_js_1.SharedPlayerContexts, { timelineContext: timelineContextValue, component: component, compositionHeight: compositionHeight, compositionWidth: compositionWidth, durationInFrames: durationInFrames, fps: fps, numberOfSharedAudioTags: numberOfSharedAudioTags, initiallyMuted: initiallyMuted, children: (0, jsx_runtime_1.jsx)(remotion_1.Internals.Timeline.SetTimelineContext.Provider, { value: setTimelineContextValue, children: (0, jsx_runtime_1.jsx)(EmitterProvider_js_1.PlayerEmitterProvider, { currentPlaybackRate: currentPlaybackRate, children: (0, jsx_runtime_1.jsx)(PlayerUI_js_1.default, { ref: rootRef, posterFillMode: posterFillMode, renderLoading: renderLoading, autoPlay: Boolean(autoPlay), loop: Boolean(loop), controls: Boolean(controls), errorFallback: errorFallback, style: style, inputProps: actualInputProps, allowFullscreen: Boolean(allowFullscreen), moveToBeginningWhenEnded: Boolean(moveToBeginningWhenEnded), clickToPlay: typeof clickToPlay === 'boolean'
142
147
  ? clickToPlay
143
- : Boolean(controls), showVolumeControls: Boolean(showVolumeControls), doubleClickToFullscreen: Boolean(doubleClickToFullscreen), spaceKeyToPlayOrPause: Boolean(spaceKeyToPlayOrPause), playbackRate: currentPlaybackRate, className: className !== null && className !== void 0 ? className : undefined, showPosterWhenUnplayed: Boolean(showPosterWhenUnplayed), showPosterWhenEnded: Boolean(showPosterWhenEnded), showPosterWhenPaused: Boolean(showPosterWhenPaused), showPosterWhenBuffering: Boolean(showPosterWhenBuffering), renderPoster: renderPoster, inFrame: inFrame !== null && inFrame !== void 0 ? inFrame : null, outFrame: outFrame !== null && outFrame !== void 0 ? outFrame : null, initiallyShowControls: initiallyShowControls !== null && initiallyShowControls !== void 0 ? initiallyShowControls : true, renderFullscreen: renderFullscreenButton !== null && renderFullscreenButton !== void 0 ? renderFullscreenButton : null, renderPlayPauseButton: renderPlayPauseButton !== null && renderPlayPauseButton !== void 0 ? renderPlayPauseButton : null, renderMuteButton: renderMuteButton !== null && renderMuteButton !== void 0 ? renderMuteButton : null, renderVolumeSlider: renderVolumeSlider !== null && renderVolumeSlider !== void 0 ? renderVolumeSlider : null, alwaysShowControls: alwaysShowControls, showPlaybackRateControl: showPlaybackRateControl, bufferStateDelayInMilliseconds: bufferStateDelayInMilliseconds !== null && bufferStateDelayInMilliseconds !== void 0 ? bufferStateDelayInMilliseconds : 300, hideControlsWhenPointerDoesntMove: hideControlsWhenPointerDoesntMove, overflowVisible: overflowVisible }) }) }) }) }));
148
+ : Boolean(controls), showVolumeControls: Boolean(showVolumeControls), doubleClickToFullscreen: Boolean(doubleClickToFullscreen), spaceKeyToPlayOrPause: Boolean(spaceKeyToPlayOrPause), playbackRate: currentPlaybackRate, className: className !== null && className !== void 0 ? className : undefined, showPosterWhenUnplayed: Boolean(showPosterWhenUnplayed), showPosterWhenEnded: Boolean(showPosterWhenEnded), showPosterWhenPaused: Boolean(showPosterWhenPaused), showPosterWhenBuffering: Boolean(showPosterWhenBuffering), renderPoster: renderPoster, inFrame: inFrame !== null && inFrame !== void 0 ? inFrame : null, outFrame: outFrame !== null && outFrame !== void 0 ? outFrame : null, initiallyShowControls: initiallyShowControls !== null && initiallyShowControls !== void 0 ? initiallyShowControls : true, renderFullscreen: renderFullscreenButton !== null && renderFullscreenButton !== void 0 ? renderFullscreenButton : null, renderPlayPauseButton: renderPlayPauseButton !== null && renderPlayPauseButton !== void 0 ? renderPlayPauseButton : null, renderMuteButton: renderMuteButton !== null && renderMuteButton !== void 0 ? renderMuteButton : null, renderVolumeSlider: renderVolumeSlider !== null && renderVolumeSlider !== void 0 ? renderVolumeSlider : null, alwaysShowControls: alwaysShowControls, showPlaybackRateControl: showPlaybackRateControl, bufferStateDelayInMilliseconds: bufferStateDelayInMilliseconds !== null && bufferStateDelayInMilliseconds !== void 0 ? bufferStateDelayInMilliseconds : 300, hideControlsWhenPointerDoesntMove: hideControlsWhenPointerDoesntMove, overflowVisible: overflowVisible, browserMediaControlsBehavior: browserMediaControlsBehavior }) }) }) }) }));
144
149
  };
145
150
  const forward = react_1.forwardRef;
146
151
  /**
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { RenderMuteButton } from './MediaVolumeSlider.js';
3
3
  import type { RenderFullscreenButton, RenderPlayPauseButton } from './PlayerControls.js';
4
+ import type { BrowserMediaControlsBehavior } from './browser-mediasession.js';
4
5
  import type { PlayerRef } from './player-methods.js';
5
6
  import type { RenderVolumeSlider } from './render-volume-slider.js';
6
7
  export type ErrorFallback = (info: {
@@ -47,5 +48,6 @@ declare const _default: React.ForwardRefExoticComponent<{
47
48
  readonly bufferStateDelayInMilliseconds: number;
48
49
  readonly hideControlsWhenPointerDoesntMove: boolean | number;
49
50
  readonly overflowVisible: boolean;
51
+ readonly browserMediaControlsBehavior: BrowserMediaControlsBehavior;
50
52
  } & React.RefAttributes<PlayerRef>>;
51
53
  export default _default;
@@ -40,7 +40,7 @@ if (reactVersion === '0') {
40
40
  throw new Error(`Version ${reactVersion} of "react" is not supported by Remotion`);
41
41
  }
42
42
  const doesReactVersionSupportSuspense = parseInt(reactVersion, 10) >= 18;
43
- const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps, clickToPlay, showVolumeControls, doubleClickToFullscreen, spaceKeyToPlayOrPause, errorFallback, playbackRate, renderLoading, renderPoster, className, moveToBeginningWhenEnded, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, showPosterWhenBuffering, inFrame, outFrame, initiallyShowControls, renderFullscreen: renderFullscreenButton, renderPlayPauseButton, renderMuteButton, renderVolumeSlider, alwaysShowControls, showPlaybackRateControl, posterFillMode, bufferStateDelayInMilliseconds, hideControlsWhenPointerDoesntMove, overflowVisible, }, ref) => {
43
+ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps, clickToPlay, showVolumeControls, doubleClickToFullscreen, spaceKeyToPlayOrPause, errorFallback, playbackRate, renderLoading, renderPoster, className, moveToBeginningWhenEnded, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, showPosterWhenBuffering, inFrame, outFrame, initiallyShowControls, renderFullscreen: renderFullscreenButton, renderPlayPauseButton, renderMuteButton, renderVolumeSlider, alwaysShowControls, showPlaybackRateControl, posterFillMode, bufferStateDelayInMilliseconds, hideControlsWhenPointerDoesntMove, overflowVisible, browserMediaControlsBehavior, }, ref) => {
44
44
  var _a, _b, _c;
45
45
  const config = remotion_1.Internals.useUnsafeVideoConfig();
46
46
  const video = remotion_1.Internals.useVideo();
@@ -69,6 +69,7 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
69
69
  inFrame,
70
70
  outFrame,
71
71
  frameRef: player.remotionInternal_currentFrameRef,
72
+ browserMediaControlsBehavior,
72
73
  });
73
74
  (0, react_1.useEffect)(() => {
74
75
  if (hasPausedToResume && !player.playing) {
@@ -0,0 +1,13 @@
1
+ import type { VideoConfig } from 'remotion';
2
+ export type BrowserMediaControlsBehavior = {
3
+ mode: 'do-nothing';
4
+ } | {
5
+ mode: 'prevent-media-session';
6
+ } | {
7
+ mode: 'register-media-session';
8
+ };
9
+ export declare const useBrowserMediaSession: ({ browserMediaControlsBehavior, videoConfig, playbackRate, }: {
10
+ browserMediaControlsBehavior: BrowserMediaControlsBehavior;
11
+ videoConfig: VideoConfig | null;
12
+ playbackRate: number;
13
+ }) => void;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useBrowserMediaSession = void 0;
4
+ const react_1 = require("react");
5
+ const use_player_js_1 = require("./use-player.js");
6
+ const useBrowserMediaSession = ({ browserMediaControlsBehavior, videoConfig, playbackRate, }) => {
7
+ const { playing, pause, play, emitter, getCurrentFrame, seek } = (0, use_player_js_1.usePlayer)();
8
+ (0, react_1.useEffect)(() => {
9
+ if (!navigator.mediaSession) {
10
+ return;
11
+ }
12
+ if (browserMediaControlsBehavior.mode === 'do-nothing') {
13
+ return;
14
+ }
15
+ if (playing) {
16
+ navigator.mediaSession.playbackState = 'playing';
17
+ }
18
+ else {
19
+ navigator.mediaSession.playbackState = 'paused';
20
+ }
21
+ }, [browserMediaControlsBehavior.mode, playing]);
22
+ (0, react_1.useEffect)(() => {
23
+ if (!navigator.mediaSession) {
24
+ return;
25
+ }
26
+ if (browserMediaControlsBehavior.mode === 'do-nothing') {
27
+ return;
28
+ }
29
+ const onTimeUpdate = () => {
30
+ if (!videoConfig) {
31
+ return;
32
+ }
33
+ if (navigator.mediaSession) {
34
+ navigator.mediaSession.setPositionState({
35
+ duration: videoConfig.durationInFrames / videoConfig.fps,
36
+ playbackRate,
37
+ position: getCurrentFrame() / videoConfig.fps,
38
+ });
39
+ }
40
+ };
41
+ emitter.addEventListener('timeupdate', onTimeUpdate);
42
+ return () => {
43
+ emitter.removeEventListener('timeupdate', onTimeUpdate);
44
+ };
45
+ }, [
46
+ browserMediaControlsBehavior.mode,
47
+ emitter,
48
+ getCurrentFrame,
49
+ playbackRate,
50
+ videoConfig,
51
+ ]);
52
+ (0, react_1.useEffect)(() => {
53
+ if (!navigator.mediaSession) {
54
+ return;
55
+ }
56
+ if (browserMediaControlsBehavior.mode === 'do-nothing') {
57
+ return;
58
+ }
59
+ navigator.mediaSession.setActionHandler('play', () => {
60
+ if (browserMediaControlsBehavior.mode === 'register-media-session') {
61
+ play();
62
+ }
63
+ });
64
+ navigator.mediaSession.setActionHandler('pause', () => {
65
+ if (browserMediaControlsBehavior.mode === 'register-media-session') {
66
+ pause();
67
+ }
68
+ });
69
+ navigator.mediaSession.setActionHandler('seekto', (event) => {
70
+ if (browserMediaControlsBehavior.mode === 'register-media-session' &&
71
+ event.seekTime !== undefined &&
72
+ videoConfig) {
73
+ seek(Math.round(event.seekTime * videoConfig.fps));
74
+ }
75
+ });
76
+ navigator.mediaSession.setActionHandler('seekbackward', () => {
77
+ if (browserMediaControlsBehavior.mode === 'register-media-session' &&
78
+ videoConfig) {
79
+ seek(Math.max(0, Math.round((getCurrentFrame() - 10) * videoConfig.fps)));
80
+ }
81
+ });
82
+ navigator.mediaSession.setActionHandler('seekforward', () => {
83
+ if (browserMediaControlsBehavior.mode === 'register-media-session' &&
84
+ videoConfig) {
85
+ seek(Math.max(videoConfig.durationInFrames - 1, Math.round((getCurrentFrame() + 10) * videoConfig.fps)));
86
+ }
87
+ });
88
+ navigator.mediaSession.setActionHandler('previoustrack', () => {
89
+ if (browserMediaControlsBehavior.mode === 'register-media-session') {
90
+ seek(0);
91
+ }
92
+ });
93
+ return () => {
94
+ navigator.mediaSession.metadata = null;
95
+ navigator.mediaSession.setActionHandler('play', null);
96
+ navigator.mediaSession.setActionHandler('pause', null);
97
+ navigator.mediaSession.setActionHandler('seekto', null);
98
+ navigator.mediaSession.setActionHandler('seekbackward', null);
99
+ navigator.mediaSession.setActionHandler('seekforward', null);
100
+ navigator.mediaSession.setActionHandler('previoustrack', null);
101
+ };
102
+ }, [
103
+ browserMediaControlsBehavior.mode,
104
+ getCurrentFrame,
105
+ pause,
106
+ play,
107
+ seek,
108
+ videoConfig,
109
+ ]);
110
+ };
111
+ exports.useBrowserMediaSession = useBrowserMediaSession;
@@ -15,5 +15,5 @@ export declare class ErrorBoundary extends React.Component<{
15
15
  hasError: Error;
16
16
  };
17
17
  componentDidCatch(error: Error): void;
18
- render(): string | number | boolean | import("react/jsx-runtime").JSX.Element | Iterable<React.ReactNode> | null | undefined;
18
+ render(): string | number | boolean | Iterable<React.ReactNode> | import("react/jsx-runtime").JSX.Element | null | undefined;
19
19
  }
@@ -32,13 +32,14 @@ export declare const PlayerInternals: {
32
32
  isBuffering: () => boolean;
33
33
  remotionInternal_currentFrameRef: React.MutableRefObject<number>;
34
34
  };
35
- usePlayback: ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, frameRef, }: {
35
+ usePlayback: ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, frameRef, browserMediaControlsBehavior, }: {
36
36
  loop: boolean;
37
37
  playbackRate: number;
38
38
  moveToBeginningWhenEnded: boolean;
39
39
  inFrame: number | null;
40
40
  outFrame: number | null;
41
41
  frameRef: React.MutableRefObject<number>;
42
+ browserMediaControlsBehavior: import("./browser-mediasession.js").BrowserMediaControlsBehavior;
42
43
  }) => void;
43
44
  useElementSize: (ref: React.RefObject<HTMLElement>, options: {
44
45
  triggerOnWindowResize: boolean;
@@ -1,8 +1,10 @@
1
- export declare const usePlayback: ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, frameRef, }: {
1
+ import type { BrowserMediaControlsBehavior } from './browser-mediasession.js';
2
+ export declare const usePlayback: ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, frameRef, browserMediaControlsBehavior, }: {
2
3
  loop: boolean;
3
4
  playbackRate: number;
4
5
  moveToBeginningWhenEnded: boolean;
5
6
  inFrame: number | null;
6
7
  outFrame: number | null;
7
8
  frameRef: React.MutableRefObject<number>;
9
+ browserMediaControlsBehavior: BrowserMediaControlsBehavior;
8
10
  }) => void;
@@ -4,10 +4,11 @@ exports.usePlayback = void 0;
4
4
  /* eslint-disable @typescript-eslint/no-use-before-define */
5
5
  const react_1 = require("react");
6
6
  const remotion_1 = require("remotion");
7
+ const browser_mediasession_js_1 = require("./browser-mediasession.js");
7
8
  const calculate_next_frame_js_1 = require("./calculate-next-frame.js");
8
9
  const is_backgrounded_js_1 = require("./is-backgrounded.js");
9
10
  const use_player_js_1 = require("./use-player.js");
10
- const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, frameRef, }) => {
11
+ const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, frameRef, browserMediaControlsBehavior, }) => {
11
12
  const config = remotion_1.Internals.useUnsafeVideoConfig();
12
13
  const frame = remotion_1.Internals.Timeline.useTimelinePosition();
13
14
  const { playing, pause, emitter } = (0, use_player_js_1.usePlayer)();
@@ -22,6 +23,12 @@ const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, ou
22
23
  if (!context) {
23
24
  throw new Error('Missing the buffering context. Most likely you have a Remotion version mismatch.');
24
25
  }
26
+ (0, browser_mediasession_js_1.useBrowserMediaSession)({
27
+ browserMediaControlsBehavior,
28
+ playbackRate,
29
+ videoConfig: config,
30
+ });
31
+ // complete code for media session API
25
32
  (0, react_1.useEffect)(() => {
26
33
  const onBufferClear = context.listenForBuffering(() => {
27
34
  buffering.current = performance.now();
@@ -520,77 +520,25 @@ var useHoverState = (ref, hideControlsWhenPointerDoesntMove) => {
520
520
  };
521
521
 
522
522
  // src/use-playback.ts
523
- import { useContext as useContext4, useEffect as useEffect5, useRef as useRef3 } from "react";
523
+ import { useContext as useContext4, useEffect as useEffect6, useRef as useRef3 } from "react";
524
524
  import { Internals as Internals5 } from "remotion";
525
525
 
526
- // src/calculate-next-frame.ts
527
- var calculateNextFrame = ({
528
- time,
529
- currentFrame: startFrame,
530
- playbackSpeed,
531
- fps,
532
- actualLastFrame,
533
- actualFirstFrame,
534
- framesAdvanced,
535
- shouldLoop
536
- }) => {
537
- const op = playbackSpeed < 0 ? Math.ceil : Math.floor;
538
- const framesToAdvance = op(time * playbackSpeed / (1000 / fps)) - framesAdvanced;
539
- const nextFrame = framesToAdvance + startFrame;
540
- const isCurrentFrameOutside = startFrame > actualLastFrame || startFrame < actualFirstFrame;
541
- const isNextFrameOutside = nextFrame > actualLastFrame || nextFrame < actualFirstFrame;
542
- const hasEnded = !shouldLoop && isNextFrameOutside && !isCurrentFrameOutside;
543
- if (playbackSpeed > 0) {
544
- if (isNextFrameOutside) {
545
- return {
546
- nextFrame: actualFirstFrame,
547
- framesToAdvance,
548
- hasEnded
549
- };
550
- }
551
- return { nextFrame, framesToAdvance, hasEnded };
552
- }
553
- if (isNextFrameOutside) {
554
- return { nextFrame: actualLastFrame, framesToAdvance, hasEnded };
555
- }
556
- return { nextFrame, framesToAdvance, hasEnded };
557
- };
558
-
559
- // src/is-backgrounded.ts
560
- import { useEffect as useEffect4, useRef } from "react";
561
- var getIsBackgrounded = () => {
562
- if (typeof document === "undefined") {
563
- return false;
564
- }
565
- return document.visibilityState === "hidden";
566
- };
567
- var useIsBackgrounded = () => {
568
- const isBackgrounded = useRef(getIsBackgrounded());
569
- useEffect4(() => {
570
- const onVisibilityChange = () => {
571
- isBackgrounded.current = getIsBackgrounded();
572
- };
573
- document.addEventListener("visibilitychange", onVisibilityChange);
574
- return () => {
575
- document.removeEventListener("visibilitychange", onVisibilityChange);
576
- };
577
- }, []);
578
- return isBackgrounded;
579
- };
526
+ // src/browser-mediasession.ts
527
+ import { useEffect as useEffect4 } from "react";
580
528
 
581
529
  // src/use-player.ts
582
- import { useCallback, useContext as useContext3, useMemo, useRef as useRef2, useState as useState3 } from "react";
530
+ import { useCallback, useContext as useContext3, useMemo, useRef, useState as useState3 } from "react";
583
531
  import { Internals as Internals4 } from "remotion";
584
532
  var usePlayer = () => {
585
533
  const [playing, setPlaying, imperativePlaying] = Internals4.Timeline.usePlayingState();
586
534
  const [hasPlayed, setHasPlayed] = useState3(false);
587
535
  const frame = Internals4.Timeline.useTimelinePosition();
588
- const playStart = useRef2(frame);
536
+ const playStart = useRef(frame);
589
537
  const setFrame = Internals4.Timeline.useTimelineSetFrame();
590
538
  const setTimelinePosition = Internals4.Timeline.useTimelineSetFrame();
591
539
  const audioContext = useContext3(Internals4.SharedAudioContext);
592
540
  const { audioAndVideoTags } = useContext3(Internals4.Timeline.TimelineContext);
593
- const frameRef = useRef2(frame);
541
+ const frameRef = useRef(frame);
594
542
  frameRef.current = frame;
595
543
  const video = Internals4.useVideo();
596
544
  const config = Internals4.useUnsafeVideoConfig();
@@ -726,6 +674,167 @@ var usePlayer = () => {
726
674
  return returnValue;
727
675
  };
728
676
 
677
+ // src/browser-mediasession.ts
678
+ var useBrowserMediaSession = ({
679
+ browserMediaControlsBehavior,
680
+ videoConfig,
681
+ playbackRate
682
+ }) => {
683
+ const { playing, pause, play, emitter, getCurrentFrame, seek } = usePlayer();
684
+ useEffect4(() => {
685
+ if (!navigator.mediaSession) {
686
+ return;
687
+ }
688
+ if (browserMediaControlsBehavior.mode === "do-nothing") {
689
+ return;
690
+ }
691
+ if (playing) {
692
+ navigator.mediaSession.playbackState = "playing";
693
+ } else {
694
+ navigator.mediaSession.playbackState = "paused";
695
+ }
696
+ }, [browserMediaControlsBehavior.mode, playing]);
697
+ useEffect4(() => {
698
+ if (!navigator.mediaSession) {
699
+ return;
700
+ }
701
+ if (browserMediaControlsBehavior.mode === "do-nothing") {
702
+ return;
703
+ }
704
+ const onTimeUpdate = () => {
705
+ if (!videoConfig) {
706
+ return;
707
+ }
708
+ if (navigator.mediaSession) {
709
+ navigator.mediaSession.setPositionState({
710
+ duration: videoConfig.durationInFrames / videoConfig.fps,
711
+ playbackRate,
712
+ position: getCurrentFrame() / videoConfig.fps
713
+ });
714
+ }
715
+ };
716
+ emitter.addEventListener("timeupdate", onTimeUpdate);
717
+ return () => {
718
+ emitter.removeEventListener("timeupdate", onTimeUpdate);
719
+ };
720
+ }, [
721
+ browserMediaControlsBehavior.mode,
722
+ emitter,
723
+ getCurrentFrame,
724
+ playbackRate,
725
+ videoConfig
726
+ ]);
727
+ useEffect4(() => {
728
+ if (!navigator.mediaSession) {
729
+ return;
730
+ }
731
+ if (browserMediaControlsBehavior.mode === "do-nothing") {
732
+ return;
733
+ }
734
+ navigator.mediaSession.setActionHandler("play", () => {
735
+ if (browserMediaControlsBehavior.mode === "register-media-session") {
736
+ play();
737
+ }
738
+ });
739
+ navigator.mediaSession.setActionHandler("pause", () => {
740
+ if (browserMediaControlsBehavior.mode === "register-media-session") {
741
+ pause();
742
+ }
743
+ });
744
+ navigator.mediaSession.setActionHandler("seekto", (event) => {
745
+ if (browserMediaControlsBehavior.mode === "register-media-session" && event.seekTime !== undefined && videoConfig) {
746
+ seek(Math.round(event.seekTime * videoConfig.fps));
747
+ }
748
+ });
749
+ navigator.mediaSession.setActionHandler("seekbackward", () => {
750
+ if (browserMediaControlsBehavior.mode === "register-media-session" && videoConfig) {
751
+ seek(Math.max(0, Math.round((getCurrentFrame() - 10) * videoConfig.fps)));
752
+ }
753
+ });
754
+ navigator.mediaSession.setActionHandler("seekforward", () => {
755
+ if (browserMediaControlsBehavior.mode === "register-media-session" && videoConfig) {
756
+ seek(Math.max(videoConfig.durationInFrames - 1, Math.round((getCurrentFrame() + 10) * videoConfig.fps)));
757
+ }
758
+ });
759
+ navigator.mediaSession.setActionHandler("previoustrack", () => {
760
+ if (browserMediaControlsBehavior.mode === "register-media-session") {
761
+ seek(0);
762
+ }
763
+ });
764
+ return () => {
765
+ navigator.mediaSession.metadata = null;
766
+ navigator.mediaSession.setActionHandler("play", null);
767
+ navigator.mediaSession.setActionHandler("pause", null);
768
+ navigator.mediaSession.setActionHandler("seekto", null);
769
+ navigator.mediaSession.setActionHandler("seekbackward", null);
770
+ navigator.mediaSession.setActionHandler("seekforward", null);
771
+ navigator.mediaSession.setActionHandler("previoustrack", null);
772
+ };
773
+ }, [
774
+ browserMediaControlsBehavior.mode,
775
+ getCurrentFrame,
776
+ pause,
777
+ play,
778
+ seek,
779
+ videoConfig
780
+ ]);
781
+ };
782
+
783
+ // src/calculate-next-frame.ts
784
+ var calculateNextFrame = ({
785
+ time,
786
+ currentFrame: startFrame,
787
+ playbackSpeed,
788
+ fps,
789
+ actualLastFrame,
790
+ actualFirstFrame,
791
+ framesAdvanced,
792
+ shouldLoop
793
+ }) => {
794
+ const op = playbackSpeed < 0 ? Math.ceil : Math.floor;
795
+ const framesToAdvance = op(time * playbackSpeed / (1000 / fps)) - framesAdvanced;
796
+ const nextFrame = framesToAdvance + startFrame;
797
+ const isCurrentFrameOutside = startFrame > actualLastFrame || startFrame < actualFirstFrame;
798
+ const isNextFrameOutside = nextFrame > actualLastFrame || nextFrame < actualFirstFrame;
799
+ const hasEnded = !shouldLoop && isNextFrameOutside && !isCurrentFrameOutside;
800
+ if (playbackSpeed > 0) {
801
+ if (isNextFrameOutside) {
802
+ return {
803
+ nextFrame: actualFirstFrame,
804
+ framesToAdvance,
805
+ hasEnded
806
+ };
807
+ }
808
+ return { nextFrame, framesToAdvance, hasEnded };
809
+ }
810
+ if (isNextFrameOutside) {
811
+ return { nextFrame: actualLastFrame, framesToAdvance, hasEnded };
812
+ }
813
+ return { nextFrame, framesToAdvance, hasEnded };
814
+ };
815
+
816
+ // src/is-backgrounded.ts
817
+ import { useEffect as useEffect5, useRef as useRef2 } from "react";
818
+ var getIsBackgrounded = () => {
819
+ if (typeof document === "undefined") {
820
+ return false;
821
+ }
822
+ return document.visibilityState === "hidden";
823
+ };
824
+ var useIsBackgrounded = () => {
825
+ const isBackgrounded = useRef2(getIsBackgrounded());
826
+ useEffect5(() => {
827
+ const onVisibilityChange = () => {
828
+ isBackgrounded.current = getIsBackgrounded();
829
+ };
830
+ document.addEventListener("visibilitychange", onVisibilityChange);
831
+ return () => {
832
+ document.removeEventListener("visibilitychange", onVisibilityChange);
833
+ };
834
+ }, []);
835
+ return isBackgrounded;
836
+ };
837
+
729
838
  // src/use-playback.ts
730
839
  var usePlayback = ({
731
840
  loop,
@@ -733,7 +842,8 @@ var usePlayback = ({
733
842
  moveToBeginningWhenEnded,
734
843
  inFrame,
735
844
  outFrame,
736
- frameRef
845
+ frameRef,
846
+ browserMediaControlsBehavior
737
847
  }) => {
738
848
  const config = Internals5.useUnsafeVideoConfig();
739
849
  const frame = Internals5.Timeline.useTimelinePosition();
@@ -746,7 +856,12 @@ var usePlayback = ({
746
856
  if (!context) {
747
857
  throw new Error("Missing the buffering context. Most likely you have a Remotion version mismatch.");
748
858
  }
749
- useEffect5(() => {
859
+ useBrowserMediaSession({
860
+ browserMediaControlsBehavior,
861
+ playbackRate,
862
+ videoConfig: config
863
+ });
864
+ useEffect6(() => {
750
865
  const onBufferClear = context.listenForBuffering(() => {
751
866
  buffering.current = performance.now();
752
867
  });
@@ -758,7 +873,7 @@ var usePlayback = ({
758
873
  onResumeClear.remove();
759
874
  };
760
875
  }, [context]);
761
- useEffect5(() => {
876
+ useEffect6(() => {
762
877
  if (!config) {
763
878
  return;
764
879
  }
@@ -862,7 +977,7 @@ var usePlayback = ({
862
977
  buffering,
863
978
  context
864
979
  ]);
865
- useEffect5(() => {
980
+ useEffect6(() => {
866
981
  const interval = setInterval(() => {
867
982
  if (lastTimeUpdateEvent.current === frameRef.current) {
868
983
  return;
@@ -872,13 +987,13 @@ var usePlayback = ({
872
987
  }, 250);
873
988
  return () => clearInterval(interval);
874
989
  }, [emitter, frameRef]);
875
- useEffect5(() => {
990
+ useEffect6(() => {
876
991
  emitter.dispatchFrameUpdate({ frame });
877
992
  }, [emitter, frame]);
878
993
  };
879
994
 
880
995
  // src/utils/use-element-size.ts
881
- import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo2, useState as useState4 } from "react";
996
+ import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo2, useState as useState4 } from "react";
882
997
  var elementSizeHooks = [];
883
998
  var updateAllElementsSizes = () => {
884
999
  for (const listener of elementSizeHooks) {
@@ -957,7 +1072,7 @@ var useElementSize = (ref, options) => {
957
1072
  };
958
1073
  });
959
1074
  }, [ref]);
960
- useEffect6(() => {
1075
+ useEffect7(() => {
961
1076
  if (!observer) {
962
1077
  return;
963
1078
  }
@@ -971,7 +1086,7 @@ var useElementSize = (ref, options) => {
971
1086
  }
972
1087
  };
973
1088
  }, [observer, ref, updateSize]);
974
- useEffect6(() => {
1089
+ useEffect7(() => {
975
1090
  if (!options.triggerOnWindowResize) {
976
1091
  return;
977
1092
  }
@@ -980,7 +1095,7 @@ var useElementSize = (ref, options) => {
980
1095
  window.removeEventListener("resize", updateSize);
981
1096
  };
982
1097
  }, [options.triggerOnWindowResize, updateSize]);
983
- useEffect6(() => {
1098
+ useEffect7(() => {
984
1099
  elementSizeHooks.push(updateSize);
985
1100
  return () => {
986
1101
  elementSizeHooks = elementSizeHooks.filter((e) => e !== updateSize);
@@ -997,7 +1112,7 @@ var useElementSize = (ref, options) => {
997
1112
  // src/Player.tsx
998
1113
  import {
999
1114
  forwardRef as forwardRef2,
1000
- useEffect as useEffect12,
1115
+ useEffect as useEffect13,
1001
1116
  useImperativeHandle as useImperativeHandle2,
1002
1117
  useLayoutEffect,
1003
1118
  useMemo as useMemo13,
@@ -1012,7 +1127,7 @@ import React8, {
1012
1127
  forwardRef,
1013
1128
  useCallback as useCallback10,
1014
1129
  useContext as useContext6,
1015
- useEffect as useEffect11,
1130
+ useEffect as useEffect12,
1016
1131
  useImperativeHandle,
1017
1132
  useMemo as useMemo11,
1018
1133
  useRef as useRef9,
@@ -1021,7 +1136,7 @@ import React8, {
1021
1136
  import { Internals as Internals10 } from "remotion";
1022
1137
 
1023
1138
  // src/PlayerControls.tsx
1024
- import { useCallback as useCallback7, useEffect as useEffect10, useMemo as useMemo8, useRef as useRef7, useState as useState10 } from "react";
1139
+ import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo8, useRef as useRef7, useState as useState10 } from "react";
1025
1140
  import { Internals as Internals9 } from "remotion";
1026
1141
 
1027
1142
  // src/DefaultPlayPauseButton.tsx
@@ -1246,18 +1361,18 @@ var MediaVolumeSlider = ({ displayVerticalVolumeSlider, renderMuteButton, render
1246
1361
  import {
1247
1362
  useCallback as useCallback5,
1248
1363
  useContext as useContext5,
1249
- useEffect as useEffect8,
1364
+ useEffect as useEffect9,
1250
1365
  useMemo as useMemo5,
1251
1366
  useState as useState8
1252
1367
  } from "react";
1253
1368
  import { Internals as Internals7 } from "remotion";
1254
1369
 
1255
1370
  // src/utils/use-component-visible.ts
1256
- import { useEffect as useEffect7, useRef as useRef5, useState as useState7 } from "react";
1371
+ import { useEffect as useEffect8, useRef as useRef5, useState as useState7 } from "react";
1257
1372
  function useComponentVisible(initialIsVisible) {
1258
1373
  const [isComponentVisible, setIsComponentVisible] = useState7(initialIsVisible);
1259
1374
  const ref = useRef5(null);
1260
- useEffect7(() => {
1375
+ useEffect8(() => {
1261
1376
  const handleClickOutside = (event) => {
1262
1377
  if (ref.current && !ref.current.contains(event.target)) {
1263
1378
  setIsComponentVisible(false);
@@ -1340,7 +1455,7 @@ var PlaybackrateOption = ({ rate, onSelect, selectedRate, keyboardSelectedRate }
1340
1455
  var PlaybackPopup = ({ setIsComponentVisible, playbackRates, canvasSize }) => {
1341
1456
  const { setPlaybackRate, playbackRate } = useContext5(Internals7.Timeline.TimelineContext);
1342
1457
  const [keyboardSelectedRate, setKeyboardSelectedRate] = useState8(playbackRate);
1343
- useEffect8(() => {
1458
+ useEffect9(() => {
1344
1459
  const listener = (e) => {
1345
1460
  e.preventDefault();
1346
1461
  if (e.key === "ArrowUp") {
@@ -1472,7 +1587,7 @@ var PlaybackrateControl = ({ playbackRates, canvasSize }) => {
1472
1587
  };
1473
1588
 
1474
1589
  // src/PlayerSeekBar.tsx
1475
- import { useCallback as useCallback6, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef6, useState as useState9 } from "react";
1590
+ import { useCallback as useCallback6, useEffect as useEffect10, useMemo as useMemo6, useRef as useRef6, useState as useState9 } from "react";
1476
1591
  import { Internals as Internals8, interpolate } from "remotion";
1477
1592
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1478
1593
  var getFrameFromX = (clientX, durationInFrames, width) => {
@@ -1561,7 +1676,7 @@ var PlayerSeekBar = ({ durationInFrames, onSeekEnd, onSeekStart, inFrame, outFra
1561
1676
  }
1562
1677
  onSeekEnd();
1563
1678
  }, [dragging, onSeekEnd, pause, play]);
1564
- useEffect9(() => {
1679
+ useEffect10(() => {
1565
1680
  if (!dragging.dragging) {
1566
1681
  return;
1567
1682
  }
@@ -1806,17 +1921,17 @@ var Controls = ({
1806
1921
  opacity: Number(shouldShow)
1807
1922
  };
1808
1923
  }, [hovered, shouldShowInitially, player.playing, alwaysShowControls]);
1809
- useEffect10(() => {
1924
+ useEffect11(() => {
1810
1925
  if (playButtonRef.current && spaceKeyToPlayOrPause) {
1811
1926
  playButtonRef.current.focus({
1812
1927
  preventScroll: true
1813
1928
  });
1814
1929
  }
1815
1930
  }, [player.playing, spaceKeyToPlayOrPause]);
1816
- useEffect10(() => {
1931
+ useEffect11(() => {
1817
1932
  setSupportsFullscreen((typeof document !== "undefined" && (document.fullscreenEnabled || document.webkitFullscreenEnabled)) ?? false);
1818
1933
  }, []);
1819
- useEffect10(() => {
1934
+ useEffect11(() => {
1820
1935
  if (shouldShowInitially === false) {
1821
1936
  return;
1822
1937
  }
@@ -2136,7 +2251,8 @@ var PlayerUI = ({
2136
2251
  posterFillMode,
2137
2252
  bufferStateDelayInMilliseconds,
2138
2253
  hideControlsWhenPointerDoesntMove,
2139
- overflowVisible
2254
+ overflowVisible,
2255
+ browserMediaControlsBehavior
2140
2256
  }, ref) => {
2141
2257
  const config = Internals10.useUnsafeVideoConfig();
2142
2258
  const video = Internals10.useVideo();
@@ -2162,15 +2278,16 @@ var PlayerUI = ({
2162
2278
  moveToBeginningWhenEnded,
2163
2279
  inFrame,
2164
2280
  outFrame,
2165
- frameRef: player.remotionInternal_currentFrameRef
2281
+ frameRef: player.remotionInternal_currentFrameRef,
2282
+ browserMediaControlsBehavior
2166
2283
  });
2167
- useEffect11(() => {
2284
+ useEffect12(() => {
2168
2285
  if (hasPausedToResume && !player.playing) {
2169
2286
  setHasPausedToResume(false);
2170
2287
  player.play();
2171
2288
  }
2172
2289
  }, [hasPausedToResume, player]);
2173
- useEffect11(() => {
2290
+ useEffect12(() => {
2174
2291
  const { current } = container;
2175
2292
  if (!current) {
2176
2293
  return;
@@ -2216,7 +2333,7 @@ var PlayerUI = ({
2216
2333
  document.exitFullscreen();
2217
2334
  }
2218
2335
  }, []);
2219
- useEffect11(() => {
2336
+ useEffect12(() => {
2220
2337
  const { current } = container;
2221
2338
  if (!current) {
2222
2339
  return;
@@ -2254,7 +2371,7 @@ var PlayerUI = ({
2254
2371
  }, [canvasSize, config]);
2255
2372
  const scale = layout?.scale ?? 1;
2256
2373
  const initialScaleIgnored = useRef9(false);
2257
- useEffect11(() => {
2374
+ useEffect12(() => {
2258
2375
  if (!initialScaleIgnored.current) {
2259
2376
  initialScaleIgnored.current = true;
2260
2377
  return;
@@ -2263,17 +2380,17 @@ var PlayerUI = ({
2263
2380
  }, [player.emitter, scale]);
2264
2381
  const { setMediaVolume, setMediaMuted } = useContext6(Internals10.SetMediaVolumeContext);
2265
2382
  const { mediaMuted, mediaVolume } = useContext6(Internals10.MediaVolumeContext);
2266
- useEffect11(() => {
2383
+ useEffect12(() => {
2267
2384
  player.emitter.dispatchVolumeChange(mediaVolume);
2268
2385
  }, [player.emitter, mediaVolume]);
2269
2386
  const isMuted = mediaMuted || mediaVolume === 0;
2270
- useEffect11(() => {
2387
+ useEffect12(() => {
2271
2388
  player.emitter.dispatchMuteChange({
2272
2389
  isMuted
2273
2390
  });
2274
2391
  }, [player.emitter, isMuted]);
2275
2392
  const [showBufferIndicator, setShowBufferState] = useState11(false);
2276
- useEffect11(() => {
2393
+ useEffect12(() => {
2277
2394
  let timeout = null;
2278
2395
  let stopped = false;
2279
2396
  const onBuffer = () => {
@@ -2444,7 +2561,7 @@ var PlayerUI = ({
2444
2561
  }
2445
2562
  }, [exitFullscreen, isFullscreen, requestFullscreen]);
2446
2563
  const { handlePointerDown, handleDoubleClick } = useClickPreventionOnDoubleClick(onSingleClick, onDoubleClick, doubleClickToFullscreen && allowFullscreen && supportsFullScreen);
2447
- useEffect11(() => {
2564
+ useEffect12(() => {
2448
2565
  if (shouldAutoplay) {
2449
2566
  player.play();
2450
2567
  setShouldAutoPlay(false);
@@ -2854,6 +2971,7 @@ var PlayerFn = ({
2854
2971
  hideControlsWhenPointerDoesntMove = true,
2855
2972
  overflowVisible = false,
2856
2973
  renderMuteButton,
2974
+ browserMediaControlsBehavior: passedBrowserMediaControlsBehavior,
2857
2975
  ...componentProps
2858
2976
  }, ref) => {
2859
2977
  if (typeof window !== "undefined") {
@@ -2929,7 +3047,7 @@ var PlayerFn = ({
2929
3047
  throw new TypeError(`'numberOfSharedAudioTags' must be an integer but got '${numberOfSharedAudioTags}' instead`);
2930
3048
  }
2931
3049
  validatePlaybackRate(currentPlaybackRate);
2932
- useEffect12(() => {
3050
+ useEffect13(() => {
2933
3051
  setCurrentPlaybackRate(playbackRate);
2934
3052
  }, [playbackRate]);
2935
3053
  useImperativeHandle2(ref, () => rootRef.current, []);
@@ -2958,6 +3076,11 @@ var PlayerFn = ({
2958
3076
  }, []);
2959
3077
  }
2960
3078
  const actualInputProps = useMemo13(() => inputProps ?? {}, [inputProps]);
3079
+ const browserMediaControlsBehavior = useMemo13(() => {
3080
+ return passedBrowserMediaControlsBehavior ?? {
3081
+ mode: "prevent-media-session"
3082
+ };
3083
+ }, [passedBrowserMediaControlsBehavior]);
2961
3084
  return /* @__PURE__ */ jsx13(Internals12.IsPlayerContextProvider, {
2962
3085
  children: /* @__PURE__ */ jsx13(SharedPlayerContexts, {
2963
3086
  timelineContext: timelineContextValue,
@@ -3006,7 +3129,8 @@ var PlayerFn = ({
3006
3129
  showPlaybackRateControl,
3007
3130
  bufferStateDelayInMilliseconds: bufferStateDelayInMilliseconds ?? 300,
3008
3131
  hideControlsWhenPointerDoesntMove,
3009
- overflowVisible
3132
+ overflowVisible,
3133
+ browserMediaControlsBehavior
3010
3134
  })
3011
3135
  })
3012
3136
  })
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/player"
4
4
  },
5
5
  "name": "@remotion/player",
6
- "version": "4.0.220",
6
+ "version": "4.0.222",
7
7
  "description": "React component for embedding a Remotion preview into your app",
8
8
  "main": "dist/cjs/index.js",
9
9
  "types": "dist/cjs/index.d.ts",
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "license": "SEE LICENSE IN LICENSE.md",
30
30
  "dependencies": {
31
- "remotion": "4.0.220"
31
+ "remotion": "4.0.222"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": ">=16.8.0",