mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
feat(player): raise the playback speed ceiling off YouTube's limit
A single speed ladder fed both players, so the 2x cap that YouTube's iframe API enforces also applied to Bunny and R2, which are plain <video> elements the browser will play far faster. Pick the ladder per provider: YouTube keeps 0.25x-2x, the native ones go up to 16x, where Chrome and Firefox clamp playbackRate. The picker labels everything past 4x as "no audio", since that is where the browsers stop pitch-correcting and drop the audio track.
This commit is contained in:
@@ -21,6 +21,10 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { resolvePublicBunnyCdnHostname } from '@/lib/bunny-cdn';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
NATIVE_SPEED_OPTIONS,
|
||||
SILENT_ABOVE_SPEED,
|
||||
} from '@/components/video-page/hooks/video-player-utils';
|
||||
import type { BunnyPlaybackState, BunnyQualityOption } from '@/components/video-page/types';
|
||||
|
||||
interface BunnyPreviewPlayerProps {
|
||||
@@ -35,8 +39,6 @@ export interface BunnyPreviewPlayerHandle {
|
||||
toggleMute: () => void;
|
||||
}
|
||||
|
||||
const SPEED_OPTIONS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
||||
|
||||
function formatTime(value: number): string {
|
||||
if (!Number.isFinite(value) || value < 0) return '0:00';
|
||||
const total = Math.floor(value);
|
||||
@@ -635,9 +637,14 @@ export const BunnyPreviewPlayer = forwardRef<BunnyPreviewPlayerHandle, BunnyPrev
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{SPEED_OPTIONS.map((speed) => (
|
||||
{NATIVE_SPEED_OPTIONS.map((speed) => (
|
||||
<DropdownMenuItem key={speed} onClick={() => handleSpeedChange(speed)}>
|
||||
{speed}x {speed === playbackSpeed ? '(Current)' : ''}
|
||||
{speed > SILENT_ABOVE_SPEED && (
|
||||
<span className="ml-auto pl-2 text-[10px] text-muted-foreground">
|
||||
no audio
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -95,6 +95,23 @@ export function timeFromClientX(
|
||||
return percentage * duration;
|
||||
}
|
||||
|
||||
// Speed ladders. YouTube is played through its iframe API, which silently
|
||||
// ignores any rate outside the list `getAvailablePlaybackRates()` returns, so
|
||||
// 2x is the ceiling there. Bunny and R2 are plain <video> elements, where the
|
||||
// ceiling is the browser's: Chrome and Firefox both clamp `playbackRate` at 16,
|
||||
// and setting more throws, so 16x is the top of the ladder.
|
||||
export const YOUTUBE_SPEED_OPTIONS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
||||
export const NATIVE_SPEED_OPTIONS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4, 6, 8, 16];
|
||||
|
||||
// Past 4x the browsers stop pitch-correcting and drop the audio track entirely.
|
||||
// The video still plays, so the fast rates are worth offering, but the picker
|
||||
// says so rather than letting a silent 8x read as a broken file.
|
||||
export const SILENT_ABOVE_SPEED = 4;
|
||||
|
||||
export function getSpeedOptionsForProvider(providerId: string | null | undefined): number[] {
|
||||
return providerId === 'youtube' ? YOUTUBE_SPEED_OPTIONS : NATIVE_SPEED_OPTIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Next or previous entry in the speed ladder, or `null` at either end. An
|
||||
* unknown current speed behaves like index -1, so stepping up lands on the
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
type AnnotationCanvasHandle,
|
||||
type AnnotationStroke,
|
||||
} from '@/components/annotation-canvas';
|
||||
import { SILENT_ABOVE_SPEED } from '@/components/video-page/hooks/video-player-utils';
|
||||
import type { BunnyQualityOption, CommentMarker } from '@/components/video-page/types';
|
||||
|
||||
interface PlayerCoreProps {
|
||||
@@ -453,9 +454,17 @@ export const PlayerCore = memo(function PlayerCore({
|
||||
<DropdownMenuItem
|
||||
key={speed}
|
||||
onClick={() => handleSpeedChange(speed)}
|
||||
className={cn(speed === playbackSpeed && 'font-bold text-primary')}
|
||||
className={cn(
|
||||
'flex items-center justify-between gap-2',
|
||||
speed === playbackSpeed && 'font-bold text-primary'
|
||||
)}
|
||||
>
|
||||
{speed}x
|
||||
{speed > SILENT_ABOVE_SPEED && (
|
||||
<span className="text-[10px] font-normal text-muted-foreground">
|
||||
no audio
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
|
||||
Reference in New Issue
Block a user