UNPKG

remotion

Version:

Make videos programmatically

279 lines (278 loc) • 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useMediaPlayback = void 0; const react_1 = require("react"); const use_audio_frame_js_1 = require("./audio/use-audio-frame.js"); const buffer_until_first_frame_js_1 = require("./buffer-until-first-frame.js"); const buffering_js_1 = require("./buffering.js"); const log_level_context_js_1 = require("./log-level-context.js"); const log_js_1 = require("./log.js"); const media_tag_current_time_timestamp_js_1 = require("./media-tag-current-time-timestamp.js"); const play_and_handle_not_allowed_error_js_1 = require("./play-and-handle-not-allowed-error.js"); const playback_logging_js_1 = require("./playback-logging.js"); const seek_js_1 = require("./seek.js"); const timeline_position_state_js_1 = require("./timeline-position-state.js"); const use_current_frame_js_1 = require("./use-current-frame.js"); const use_media_buffering_js_1 = require("./use-media-buffering.js"); const use_remotion_environment_js_1 = require("./use-remotion-environment.js"); const use_request_video_callback_time_js_1 = require("./use-request-video-callback-time.js"); const use_video_config_js_1 = require("./use-video-config.js"); const get_current_time_js_1 = require("./video/get-current-time.js"); const warn_about_non_seekable_media_js_1 = require("./warn-about-non-seekable-media.js"); const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybackRate, onlyWarnForMediaSeekingError, acceptableTimeshift, pauseWhenBuffering, isPremounting, isPostmounting, onAutoPlayError, }) => { const { playbackRate: globalPlaybackRate } = (0, timeline_position_state_js_1.useTimelineContext)(); const frame = (0, use_current_frame_js_1.useCurrentFrame)(); const absoluteFrame = (0, timeline_position_state_js_1.useTimelinePosition)(); const [playing] = (0, timeline_position_state_js_1.usePlayingState)(); const buffering = (0, react_1.useContext)(buffering_js_1.BufferingContextReact); const { fps } = (0, use_video_config_js_1.useVideoConfig)(); const mediaStartsAt = (0, use_audio_frame_js_1.useMediaStartsAt)(); const lastSeekDueToShift = (0, react_1.useRef)(null); const lastSeek = (0, react_1.useRef)(null); const logLevel = (0, log_level_context_js_1.useLogLevel)(); const mountTime = (0, log_level_context_js_1.useMountTime)(); if (!buffering) { throw new Error('useMediaPlayback must be used inside a <BufferingContext>'); } const isVariableFpsVideoMap = (0, react_1.useRef)({}); const onVariableFpsVideoDetected = (0, react_1.useCallback)(() => { if (!src) { return; } if (isVariableFpsVideoMap.current[src]) { return; } log_js_1.Log.verbose({ logLevel, tag: null }, `Detected ${src} as a variable FPS video. Disabling buffering while seeking.`); isVariableFpsVideoMap.current[src] = true; }, [logLevel, src]); const rvcCurrentTime = (0, use_request_video_callback_time_js_1.useRequestVideoCallbackTime)({ mediaRef, mediaType, lastSeek, onVariableFpsVideoDetected, }); const mediaTagCurrentTime = (0, media_tag_current_time_timestamp_js_1.useCurrentTimeOfMediaTagWithUpdateTimeStamp)(mediaRef); const desiredUnclampedTime = (0, get_current_time_js_1.getMediaTime)({ frame, playbackRate: localPlaybackRate, startFrom: -mediaStartsAt, fps, }); const isMediaTagBuffering = (0, use_media_buffering_js_1.useMediaBuffering)({ element: mediaRef, shouldBuffer: pauseWhenBuffering, isPremounting, isPostmounting, logLevel, mountTime, src: src !== null && src !== void 0 ? src : null, }); const { bufferUntilFirstFrame, isBuffering } = (0, buffer_until_first_frame_js_1.useBufferUntilFirstFrame)({ mediaRef, mediaType, onVariableFpsVideoDetected, pauseWhenBuffering, logLevel, mountTime, }); const playbackRate = localPlaybackRate * globalPlaybackRate; const acceptableTimeShiftButLessThanDuration = (() => { var _a; // In Safari, it seems to lag behind mostly around ~0.4 seconds const DEFAULT_ACCEPTABLE_TIMESHIFT_WITH_NORMAL_PLAYBACK = 0.45; // If there is amplification, the acceptable timeshift is higher const DEFAULT_ACCEPTABLE_TIMESHIFT_WITH_AMPLIFICATION = DEFAULT_ACCEPTABLE_TIMESHIFT_WITH_NORMAL_PLAYBACK + 0.2; const defaultAcceptableTimeshift = DEFAULT_ACCEPTABLE_TIMESHIFT_WITH_AMPLIFICATION; // For short audio, a lower acceptable time shift is used if ((_a = mediaRef.current) === null || _a === void 0 ? void 0 : _a.duration) { return Math.min(mediaRef.current.duration, acceptableTimeshift !== null && acceptableTimeshift !== void 0 ? acceptableTimeshift : defaultAcceptableTimeshift); } return acceptableTimeshift !== null && acceptableTimeshift !== void 0 ? acceptableTimeshift : defaultAcceptableTimeshift; })(); const isPlayerBuffering = (0, buffering_js_1.useIsPlayerBuffering)(buffering); (0, react_1.useEffect)(() => { var _a, _b, _c, _d, _e; if ((_a = mediaRef.current) === null || _a === void 0 ? void 0 : _a.paused) { return; } if (!playing) { (0, playback_logging_js_1.playbackLogging)({ logLevel, tag: 'pause', message: `Pausing ${(_b = mediaRef.current) === null || _b === void 0 ? void 0 : _b.src} because ${isPremounting ? 'media is premounting' : isPostmounting ? 'media is postmounting' : 'Player is not playing'}`, mountTime, }); (_c = mediaRef.current) === null || _c === void 0 ? void 0 : _c.pause(); return; } const isMediaTagBufferingOrStalled = isMediaTagBuffering || isBuffering(); const playerBufferingNotStateButLive = buffering.buffering.current; if (playerBufferingNotStateButLive && !isMediaTagBufferingOrStalled) { (0, playback_logging_js_1.playbackLogging)({ logLevel, tag: 'pause', message: `Pausing ${(_d = mediaRef.current) === null || _d === void 0 ? void 0 : _d.src} because player is buffering but media tag is not`, mountTime, }); (_e = mediaRef.current) === null || _e === void 0 ? void 0 : _e.pause(); } }, [ isBuffering, isMediaTagBuffering, buffering, isPlayerBuffering, isPremounting, logLevel, mediaRef, mediaType, mountTime, playing, isPostmounting, ]); const env = (0, use_remotion_environment_js_1.useRemotionEnvironment)(); // This must be a useLayoutEffect, because afterwards, useVolume() looks at the playbackRate // and it is also in a useLayoutEffect. (0, react_1.useLayoutEffect)(() => { const playbackRateToSet = Math.max(0, playbackRate); if (mediaRef.current && mediaRef.current.playbackRate !== playbackRateToSet) { mediaRef.current.playbackRate = playbackRateToSet; } }, [mediaRef, playbackRate]); (0, react_1.useEffect)(() => { var _a, _b, _c; const tagName = mediaType === 'audio' ? '<Html5Audio>' : '<Html5Video>'; if (!mediaRef.current) { throw new Error(`No ${mediaType} ref found`); } if (!src) { throw new Error(`No 'src' attribute was passed to the ${tagName} element.`); } const { duration } = mediaRef.current; const shouldBeTime = !Number.isNaN(duration) && Number.isFinite(duration) ? Math.min(duration, desiredUnclampedTime) : desiredUnclampedTime; const mediaTagTime = mediaTagCurrentTime.current.time; const rvcTime = (_b = (_a = rvcCurrentTime.current) === null || _a === void 0 ? void 0 : _a.time) !== null && _b !== void 0 ? _b : null; const isVariableFpsVideo = isVariableFpsVideoMap.current[src]; const timeShiftMediaTag = Math.abs(shouldBeTime - mediaTagTime); const timeShiftRvcTag = rvcTime ? Math.abs(shouldBeTime - rvcTime) : null; const mostRecentTimeshift = ((_c = rvcCurrentTime.current) === null || _c === void 0 ? void 0 : _c.lastUpdate) && rvcCurrentTime.current.time > mediaTagCurrentTime.current.lastUpdate ? timeShiftRvcTag : timeShiftMediaTag; const timeShift = timeShiftRvcTag && !isVariableFpsVideo ? mostRecentTimeshift : timeShiftMediaTag; if (timeShift > acceptableTimeShiftButLessThanDuration && lastSeekDueToShift.current !== shouldBeTime) { // If scrubbing around, adjust timing // or if time shift is bigger than 0.45sec lastSeek.current = (0, seek_js_1.seek)({ mediaRef: mediaRef.current, time: shouldBeTime, logLevel, why: `because time shift is too big. shouldBeTime = ${shouldBeTime}, isTime = ${mediaTagTime}, requestVideoCallbackTime = ${rvcTime}, timeShift = ${timeShift}${isVariableFpsVideo ? ', isVariableFpsVideo = true' : ''}, isPremounting = ${isPremounting}, isPostmounting = ${isPostmounting}, pauseWhenBuffering = ${pauseWhenBuffering}`, mountTime, }); lastSeekDueToShift.current = lastSeek.current; if (playing) { if (playbackRate > 0) { bufferUntilFirstFrame(shouldBeTime); } if (mediaRef.current.paused) { (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)({ mediaRef, mediaType, onAutoPlayError, logLevel, mountTime, reason: 'player is playing but media tag is paused, and just seeked', isPlayer: env.isPlayer, }); } } if (!onlyWarnForMediaSeekingError) { (0, warn_about_non_seekable_media_js_1.warnAboutNonSeekableMedia)(mediaRef.current, onlyWarnForMediaSeekingError ? 'console-warning' : 'console-error'); } return; } const seekThreshold = playing ? 0.15 : 0.01; // Only perform a seek if the time is not already the same. // Chrome rounds to 6 digits, so 0.033333333 -> 0.033333, // therefore a threshold is allowed. // Refer to the https://github.com/remotion-dev/video-buffering-example // which is fixed by only seeking conditionally. const makesSenseToSeek = Math.abs(mediaRef.current.currentTime - shouldBeTime) > seekThreshold; const isMediaTagBufferingOrStalled = isMediaTagBuffering || isBuffering(); const isSomethingElseBuffering = buffering.buffering.current && !isMediaTagBufferingOrStalled; if (!playing || isSomethingElseBuffering) { if (makesSenseToSeek) { lastSeek.current = (0, seek_js_1.seek)({ mediaRef: mediaRef.current, time: shouldBeTime, logLevel, why: `not playing or something else is buffering. time offset is over seek threshold (${seekThreshold})`, mountTime, }); } return; } if (!playing || buffering.buffering.current) { return; } // We now assured we are in playing state and not buffering const pausedCondition = mediaRef.current.paused && !mediaRef.current.ended; const firstFrameCondition = absoluteFrame === 0; if (pausedCondition || firstFrameCondition) { const reason = pausedCondition ? 'media tag is paused' : 'absolute frame is 0'; if (makesSenseToSeek) { lastSeek.current = (0, seek_js_1.seek)({ mediaRef: mediaRef.current, time: shouldBeTime, logLevel, why: `is over timeshift threshold (threshold = ${seekThreshold}) and ${reason}`, mountTime, }); } (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)({ mediaRef, mediaType, onAutoPlayError, logLevel, mountTime, reason: `player is playing and ${reason}`, isPlayer: env.isPlayer, }); if (!isVariableFpsVideo && playbackRate > 0) { bufferUntilFirstFrame(shouldBeTime); } } }, [ absoluteFrame, acceptableTimeShiftButLessThanDuration, bufferUntilFirstFrame, buffering.buffering, rvcCurrentTime, logLevel, desiredUnclampedTime, isBuffering, isMediaTagBuffering, mediaRef, mediaType, onlyWarnForMediaSeekingError, playbackRate, playing, src, onAutoPlayError, isPremounting, isPostmounting, pauseWhenBuffering, mountTime, mediaTagCurrentTime, env.isPlayer, ]); }; exports.useMediaPlayback = useMediaPlayback;