Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a144b486b |
@@ -54,6 +54,7 @@ import type {
|
||||
SubtitleMode,
|
||||
} from "../types";
|
||||
import { toMediaTime, toPlayerTime } from "../utils/mediaTimeline";
|
||||
import { videoElementClassName } from "../utils/videoElementClassName";
|
||||
import { buildWatchTogetherInviteUrl } from "@/lib/watchTogether";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -2279,7 +2280,7 @@ export function VideoPlayer({
|
||||
media timeline as restarted HLS playback. */}
|
||||
<video
|
||||
ref={videoRef}
|
||||
className={isDetached ? "h-full w-full" : "absolute inset-0 h-full w-full"}
|
||||
className={videoElementClassName(isDetached)}
|
||||
onClick={displayMode === "postroll" ? undefined : handlePlayPause}
|
||||
playsInline
|
||||
style={!isPlayerReady ? { visibility: "hidden" } : undefined}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { videoElementClassName } from "./videoElementClassName";
|
||||
|
||||
describe("videoElementClassName", () => {
|
||||
// object-contain is the fix for issue #209: without it the video defaults to
|
||||
// object-fit: fill and stretches, skewing the video and its subtitle overlays
|
||||
// (VTT/PGS/ASS all anchor to the contained video area) whenever the player box
|
||||
// is not the video's aspect ratio.
|
||||
it("applies object-contain in the inline/fullscreen layout", () => {
|
||||
const cls = videoElementClassName(false);
|
||||
expect(cls).toContain("object-contain");
|
||||
expect(cls).toContain("absolute");
|
||||
expect(cls).toContain("inset-0");
|
||||
expect(cls).toContain("h-full");
|
||||
expect(cls).toContain("w-full");
|
||||
});
|
||||
|
||||
it("applies object-contain in the detached/floating layout", () => {
|
||||
const cls = videoElementClassName(true);
|
||||
expect(cls).toContain("object-contain");
|
||||
expect(cls).toContain("h-full");
|
||||
expect(cls).toContain("w-full");
|
||||
// The detached player sizes itself inside an aspect-video container, so it
|
||||
// must not pin to the fullscreen frame with absolute inset-0.
|
||||
expect(cls).not.toContain("absolute");
|
||||
expect(cls).not.toContain("inset-0");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Computes the className for the player's `<video>` element.
|
||||
*
|
||||
* `object-contain` is load-bearing, not cosmetic: replaced elements default to
|
||||
* `object-fit: fill`, which stretches the decoded frame to the element box and
|
||||
* distorts the picture whenever the player box is not the video's aspect ratio.
|
||||
* The subtitle systems all anchor to the *contained* video area —
|
||||
* `useSubtitlePositionStyle` (VTT), libpgs (`aspectRatio: "contain"`), and
|
||||
* JASSUB's contain-based canvas geometry — so a stretched video makes the video
|
||||
* and its subtitles render skewed relative to each other (issue #209). Keeping
|
||||
* the video on `object-fit: contain` matches that shared assumption.
|
||||
*
|
||||
* @param isDetached - true for the floating/mini player (no absolute fill), false
|
||||
* for the inline/fullscreen player that fills its positioned container.
|
||||
*/
|
||||
export function videoElementClassName(isDetached: boolean): string {
|
||||
return isDetached
|
||||
? "h-full w-full object-contain"
|
||||
: "absolute inset-0 h-full w-full object-contain";
|
||||
}
|
||||
Reference in New Issue
Block a user