disable playback speed in watchparty

This commit is contained in:
Pas 2025-06-05 12:25:22 -06:00
parent 2b9680313c
commit e8e9352e88
5 changed files with 51 additions and 33 deletions

View file

@ -483,7 +483,8 @@
},
"playback": {
"speedLabel": "Playback speed",
"title": "Playback settings"
"title": "Playback settings",
"disabled": "(Disabled in watch party)"
},
"quality": {
"automaticLabel": "Automatic quality",

View file

@ -1,5 +1,5 @@
import classNames from "classnames";
import { useCallback } from "react";
import { useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Toggle } from "@/components/buttons/Toggle";
@ -7,11 +7,13 @@ import { Menu } from "@/components/player/internals/ContextMenu";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { usePlayerStore } from "@/stores/player/store";
import { usePreferencesStore } from "@/stores/preferences";
import { useWatchPartyStore } from "@/stores/watchParty";
function ButtonList(props: {
options: number[];
selected: number;
onClick: (v: any) => void;
disabled?: boolean;
}) {
return (
<div className="flex items-center bg-video-context-light/10 p-1 rounded-lg">
@ -19,11 +21,13 @@ function ButtonList(props: {
return (
<button
type="button"
disabled={props.disabled}
className={classNames(
"w-full px-2 py-1 rounded-md tabbable",
props.selected === option
? "bg-video-context-light/20 text-white"
: null,
props.disabled ? "opacity-50 cursor-not-allowed" : null,
)}
onClick={() => props.onClick(option)}
key={option}
@ -43,14 +47,23 @@ export function PlaybackSettingsView({ id }: { id: string }) {
const display = usePlayerStore((s) => s.display);
const enableThumbnails = usePreferencesStore((s) => s.enableThumbnails);
const setEnableThumbnails = usePreferencesStore((s) => s.setEnableThumbnails);
const isInWatchParty = useWatchPartyStore((s) => s.enabled);
const setPlaybackRate = useCallback(
(v: number) => {
if (isInWatchParty) return; // Don't allow changes in watch party
display?.setPlaybackRate(v);
},
[display],
[display, isInWatchParty],
);
// Force 1x speed in watch party
useEffect(() => {
if (isInWatchParty && display && playbackRate !== 1) {
display.setPlaybackRate(1);
}
}, [isInWatchParty, display, playbackRate]);
const options = [0.25, 0.5, 1, 1.5, 2];
return (
@ -62,11 +75,17 @@ export function PlaybackSettingsView({ id }: { id: string }) {
<div className="space-y-4 mt-3">
<Menu.FieldTitle>
{t("player.menus.playback.speedLabel")}
{isInWatchParty && (
<span className="text-sm text-type-secondary ml-2">
{t("player.menus.playback.disabled")}
</span>
)}
</Menu.FieldTitle>
<ButtonList
options={options}
selected={playbackRate}
selected={isInWatchParty ? 1 : playbackRate}
onClick={setPlaybackRate}
disabled={isInWatchParty}
/>
</div>
</Menu.Section>

View file

@ -7,6 +7,7 @@ import { useOverlayStack } from "@/stores/interface/overlayStack";
import { usePlayerStore } from "@/stores/player/store";
import { useSubtitleStore } from "@/stores/subtitles";
import { useEmpheralVolumeStore } from "@/stores/volume";
import { useWatchPartyStore } from "@/stores/watchParty";
export function KeyboardEvents() {
const router = useOverlayRouter("");
@ -16,6 +17,7 @@ export function KeyboardEvents() {
const mediaPlaying = usePlayerStore((s) => s.mediaPlaying);
const time = usePlayerStore((s) => s.progress.time);
const { setVolume, toggleMute } = useVolume();
const isInWatchParty = useWatchPartyStore((s) => s.enabled);
const { toggleLastUsed } = useCaptions();
const setShowVolume = useEmpheralVolumeStore((s) => s.setShowVolume);
@ -48,7 +50,9 @@ export function KeyboardEvents() {
delay,
setShowDelayIndicator,
setCurrentOverlay,
isInWatchParty,
});
useEffect(() => {
dataRef.current = {
setShowVolume,
@ -67,6 +71,7 @@ export function KeyboardEvents() {
delay,
setShowDelayIndicator,
setCurrentOverlay,
isInWatchParty,
};
}, [
setShowVolume,
@ -85,6 +90,7 @@ export function KeyboardEvents() {
delay,
setShowDelayIndicator,
setCurrentOverlay,
isInWatchParty,
]);
useEffect(() => {
@ -116,8 +122,8 @@ export function KeyboardEvents() {
);
if (keyL === "m") dataRef.current.toggleMute();
// Video playback speed
if (k === ">" || k === "<") {
// Video playback speed - disabled in watch party
if ((k === ">" || k === "<") && !dataRef.current.isInWatchParty) {
const options = [0.25, 0.5, 1, 1.5, 2];
let idx = options.indexOf(dataRef.current.mediaPlaying?.playbackRate);
if (idx === -1) idx = options.indexOf(1);

View file

@ -17,7 +17,6 @@ interface RoomUser {
isPaused: boolean;
time: number;
duration: number;
playbackRate: number;
};
content: {
title: string;
@ -80,10 +79,6 @@ export function useWatchPartySync(
const display = usePlayerStore((s) => s.display);
const currentTime = usePlayerStore((s) => s.progress.time);
const isPlaying = usePlayerStore((s) => s.mediaPlaying.isPlaying);
const currentPlaybackRate = usePlayerStore(
(s) => s.mediaPlaying.playbackRate,
);
// Get watch party state
const { roomCode, isHost, enabled, enableAsGuest } = useWatchPartyStore();
@ -169,10 +164,6 @@ export function useWatchPartySync(
const predictedHostTime = getPredictedHostTime();
const difference = currentTime - predictedHostTime;
// Handle playback rate sync
const needsPlaybackRateSync =
hostUser.player.playbackRate !== currentPlaybackRate;
// Handle time sync
const activeThreshold = isPlaying ? 2 : 5;
const needsTimeSync = Math.abs(difference) > activeThreshold;
@ -188,21 +179,10 @@ export function useWatchPartySync(
Math.abs(hostUser.player.time - state.previousHostTime) > 5;
// Sync if needed
if (
(needsTimeSync ||
needsPlayStateSync ||
needsJumpSync ||
needsPlaybackRateSync) &&
!isSyncing
) {
if ((needsTimeSync || needsPlayStateSync || needsJumpSync) && !isSyncing) {
state.syncInProgress = true;
setIsSyncing(true);
// Sync playback rate first if needed
if (needsPlaybackRateSync) {
display.setPlaybackRate(hostUser.player.playbackRate);
}
// Sync time
display.setTime(predictedHostTime);
@ -229,7 +209,6 @@ export function useWatchPartySync(
hostUser,
isHost,
currentTime,
currentPlaybackRate,
display,
isSyncing,
getPredictedHostTime,
@ -262,7 +241,6 @@ export function useWatchPartySync(
isPaused: latestStatus.player.isPaused,
time: latestStatus.player.time,
duration: latestStatus.player.duration,
playbackRate: latestStatus.player.playbackRate,
},
content: {
title: latestStatus.content.title,

View file

@ -1,6 +1,8 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { usePlayerStore } from "@/stores/player/store";
interface WatchPartyStore {
// Whether the watch party feature is enabled
enabled: boolean;
@ -27,6 +29,14 @@ const generateRoomCode = (): string => {
return Math.floor(1000 + Math.random() * 9000).toString();
};
// Helper function to reset playback rate to 1x
const resetPlaybackRate = () => {
const display = usePlayerStore.getState().display;
if (display) {
display.setPlaybackRate(1);
}
};
export const useWatchPartyStore = create<WatchPartyStore>()(
persist(
(set) => ({
@ -35,19 +45,23 @@ export const useWatchPartyStore = create<WatchPartyStore>()(
isHost: false,
showStatusOverlay: false,
enableAsHost: () =>
enableAsHost: () => {
resetPlaybackRate();
set(() => ({
enabled: true,
roomCode: generateRoomCode(),
isHost: true,
})),
}));
},
enableAsGuest: (code: string) =>
enableAsGuest: (code: string) => {
resetPlaybackRate();
set(() => ({
enabled: true,
roomCode: code,
isHost: false,
})),
}));
},
updateRoomCode: (code: string) =>
set((state) => ({