Skip to content

Commit

Permalink
Add volume control
Browse files Browse the repository at this point in the history
  • Loading branch information
Dennis Benz committed Sep 27, 2023
1 parent 1b83d1d commit 4f28fb1
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 7 deletions.
4 changes: 4 additions & 0 deletions src/i18n/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"time-duration-tooltip": "Videodauer",
"duration-aria": "Dauer",
"time-aria": "Aktuelle Zeit",
"mutebutton-tooltip": "Video stummschalten",
"unmutebutton-tooltip": "Stummschaltung aufheben",
"volume-tooltip": "Lautstärke: {{current}}%",
"volumeSlider-aria": "Anpassen der Videolautstärke.",
"comError-text": "Bei der Kommunikation mit Opencast ist ein Problem aufgetreten.",
"loadError-text": "Beim Laden des Videos ist ein Fehler aufgetreten.",
"durationError-text": "Opencast konnte die Video-Dauer nicht angeben.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
"time-duration-tooltip": "Video duration",
"duration-aria": "Duration",
"time-aria": "Current time",
"mutebutton-tooltip": "Mute video",
"unmutebutton-tooltip": "Unmute video",
"volume-tooltip": "Adjust volume: {{current}}%",
"volumeSlider-aria": "Adjust the volume level of the video.",
"comError-text": "A problem occurred during communication with Opencast.",
"loadError-text": "An error has occurred loading this video.",
"durationError-text": "Opencast failed to provide the video duration.",
Expand Down
6 changes: 5 additions & 1 deletion src/main/Cutting.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect } from "react";
import CuttingActions from "./CuttingActions"
import Timeline from './Timeline';
import { fetchVideoInformation, selectCurrentlyAt, selectDuration, selectIsPlaying, selectIsPlayPreview, selectTitle, setClickTriggered, setCurrentlyAt, setIsPlaying, setIsPlayPreview } from '../redux/videoSlice';
import { fetchVideoInformation, selectCurrentlyAt, selectDuration, selectIsPlaying, selectIsMuted, selectVolume, selectIsPlayPreview, selectTitle, setClickTriggered, setCurrentlyAt, setIsPlaying, setIsMuted, setVolume, setIsPlayPreview } from '../redux/videoSlice';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { AppDispatch } from '../redux/store';
Expand Down Expand Up @@ -71,8 +71,12 @@ const Cutting: React.FC = () => {
<VideoControls
selectCurrentlyAt={selectCurrentlyAt}
selectIsPlaying={selectIsPlaying}
selectIsMuted={selectIsMuted}
selectVolume={selectVolume}
selectIsPlayPreview={selectIsPlayPreview}
setIsPlaying={setIsPlaying}
setIsMuted={setIsMuted}
setVolume={setVolume}
setIsPlayPreview={setIsPlayPreview}
/>
</div>
Expand Down
8 changes: 7 additions & 1 deletion src/main/SubtitleVideoArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { selectCurrentlyAt,
selectIsPlayPreview,
setIsPlayPreview,
setCurrentlyAtAndTriggerPreview} from "../redux/subtitleSlice";
import { selectVideos } from "../redux/videoSlice";
import {selectIsMuted, selectVideos, selectVolume, setIsMuted, setVolume} from "../redux/videoSlice";
import { Flavor } from "../types";
import { settings } from "../config";
import { useTranslation } from "react-i18next";
Expand Down Expand Up @@ -122,11 +122,13 @@ const SubtitleVideoArea : React.FC = () => {
first={true}
last={true}
selectIsPlaying={selectIsPlaying}
selectIsMuted={selectIsMuted}
selectCurrentlyAtInSeconds={selectCurrentlyAtInSeconds}
selectPreviewTriggered={selectPreviewTriggered}
selectClickTriggered={selectClickTriggered}
selectAspectRatio={selectAspectRatio}
setIsPlaying={setIsPlaying}
selectVolume={selectVolume}
setPreviewTriggered={setPreviewTriggered}
setClickTriggered={setClickTriggered}
setCurrentlyAt={setCurrentlyAtAndTriggerPreview}
Expand All @@ -135,8 +137,12 @@ const SubtitleVideoArea : React.FC = () => {
<VideoControls
selectCurrentlyAt={selectCurrentlyAt}
selectIsPlaying={selectIsPlaying}
selectIsMuted={selectIsMuted}
selectVolume={selectVolume}
selectIsPlayPreview={selectIsPlayPreview}
setIsPlaying={setIsPlaying}
setIsMuted={setIsMuted}
setVolume={setVolume}
setIsPlayPreview={setIsPlayPreview}
/>
</div>
Expand Down
6 changes: 5 additions & 1 deletion src/main/Thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { selectOriginalThumbnails, selectVideos, selectTracks, setHasChanges, se
import { Track } from "../types";
import Timeline from "./Timeline";
import {
selectIsPlaying, selectCurrentlyAt, setIsPlaying, selectIsPlayPreview, setIsPlayPreview, setClickTriggered, setCurrentlyAt
selectIsPlaying, selectIsMuted, selectVolume, selectCurrentlyAt, setIsPlaying, selectIsPlayPreview, setIsPlayPreview, setClickTriggered, setIsMuted, setVolume, setCurrentlyAt
} from '../redux/videoSlice'
import { ThemedTooltip } from "./Tooltip";
import VideoPlayers from "./VideoPlayers";
Expand Down Expand Up @@ -120,8 +120,12 @@ const Thumbnail : React.FC = () => {
<VideoControls
selectCurrentlyAt={selectCurrentlyAt}
selectIsPlaying={selectIsPlaying}
selectIsMuted={selectIsMuted}
selectVolume={selectVolume}
selectIsPlayPreview={selectIsPlayPreview}
setIsPlaying={setIsPlaying}
setIsMuted={setIsMuted}
setVolume={setVolume}
setIsPlayPreview={setIsPlayPreview}
/>
</div>
Expand Down
121 changes: 120 additions & 1 deletion src/main/VideoControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from "react";
import { css } from '@emotion/react'

import { FaToggleOn, FaToggleOff } from "react-icons/fa";
import { LuPlay, LuPause } from "react-icons/lu";
import { LuPlay, LuPause, LuVolume2, LuVolumeX } from "react-icons/lu";

import { useSelector, useDispatch } from 'react-redux';
import {
Expand All @@ -22,6 +22,7 @@ import { ActionCreatorWithPayload } from "@reduxjs/toolkit";
import { ThemedTooltip } from "./Tooltip";
import { Theme, useTheme } from "../themes";
import { useHotkeys } from "react-hotkeys-hook";
import { Slider } from "@mui/material";

/**
* Contains controls for manipulating multiple video players at once
Expand All @@ -30,14 +31,22 @@ import { useHotkeys } from "react-hotkeys-hook";
const VideoControls: React.FC<{
selectCurrentlyAt: (state: RootState) => number,
selectIsPlaying: (state: RootState) => boolean,
selectIsMuted: (state: RootState) => boolean,
selectVolume: (state: RootState) => number,
selectIsPlayPreview: (state: RootState) => boolean,
setIsPlaying: ActionCreatorWithPayload<boolean, string>,
setIsMuted: ActionCreatorWithPayload<boolean, string>,
setVolume: ActionCreatorWithPayload<number, string>,
setIsPlayPreview: ActionCreatorWithPayload<boolean, string>,
}> = ({
selectCurrentlyAt,
selectIsPlaying,
selectIsMuted,
selectVolume,
selectIsPlayPreview,
setIsPlaying,
setIsMuted,
setVolume,
setIsPlayPreview
}) => {

Expand Down Expand Up @@ -80,6 +89,12 @@ const VideoControls: React.FC<{
selectIsPlaying={selectIsPlaying}
setIsPlaying={setIsPlaying}
/>
<VolumeSlider
selectIsMuted={selectIsMuted}
setIsMuted={setIsMuted}
selectVolume={selectVolume}
setVolume={setVolume}
/>
<div css={rightSideBoxStyle}>
<PreviewMode
selectIsPlayPreview={selectIsPlayPreview}
Expand Down Expand Up @@ -264,4 +279,108 @@ const TimeDisplay: React.FC<{
);
}

const VolumeSlider: React.FC<{
selectIsMuted: (state: RootState) => boolean,
setIsMuted: ActionCreatorWithPayload<boolean, string>,
selectVolume: (state: RootState) => number,
setVolume: ActionCreatorWithPayload<number, string>,
}> = ({
selectIsMuted,
setIsMuted,
selectVolume,
setVolume
}) => {

const { t } = useTranslation();

// Init redux variables
const dispatch = useDispatch();
const isMuted = useSelector(selectIsMuted)
const volume = useSelector(selectVolume)
const theme = useTheme();

// Toggle video mute
const switchIsMuted = () => {
// Increase volume when unmuting and no volume was set before
if (volume === 0 && isMuted) {
dispatch(setVolume(1));
}

dispatch(setIsMuted(!isMuted));
}

const volumeOnChange = (_event: Event, newValue: number | number[]) => {
// Disable mute when silder is moved
if (isMuted) {
dispatch(setIsMuted(false));
}

// Get first value if array given
if (Array.isArray(newValue)) {
newValue = newValue[0];
}

// Enable mute if no volume is set
if (newValue === 0) {
dispatch(setIsMuted(true));
}

dispatch(setVolume(Number(newValue)));
}

const volumeStyle = css({
display: 'flex',
flexDirection: 'row',
gap: '15px',
justifyContent: 'center',
alignItems: 'center'
})

const sliderStyle = css({
width: '80px',
"& .MuiSlider-thumb": {
color: `${theme.slider_thumb_color}`,
"&:hover, &.Mui-focusVisible, &.Mui-active": {
boxShadow: `${theme.slider_thumb_shadow}`,
},
},
"& .MuiSlider-rail": {
color: `${theme.slider_track_color}`,
},
"& .MuiSlider-track": {
color: `${theme.slider_track_color}`,
},
})

const volumeIconStyle = css({
fontSize: 24,
paddingLeft: '3px',
})

return (
<div css={volumeStyle}>
<ThemedTooltip title={isMuted ? t("video.unmutebutton-tooltip") : t("video.mutebutton-tooltip")}>
<div css={[basicButtonStyle(theme)]}
role="button" aria-pressed={isMuted} tabIndex={0} aria-hidden={false}
aria-label={t("video.mutebutton-tooltip")}
onClick={switchIsMuted}>
{isMuted ? <LuVolumeX css={volumeIconStyle} /> : <LuVolume2 css={volumeIconStyle} />}
</div>
</ThemedTooltip>
<ThemedTooltip title={t("video.volume-tooltip", { current: Math.trunc(volume * 100) })}>
<Slider
css={sliderStyle}
min={0}
max={1}
step={0.01}
value={isMuted ? 0 : volume}
onChange={volumeOnChange}
aria-label={t("video.volume-tooltip", { current: Math.trunc(volume * 100) })}
valueLabelDisplay="off"
/>
</ThemedTooltip>
</div>
);
}

export default VideoControls
13 changes: 11 additions & 2 deletions src/main/VideoPlayers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { css } from '@emotion/react'

import { useSelector, useDispatch } from 'react-redux';
import {
selectIsPlaying, selectCurrentlyAtInSeconds, setIsPlaying,
selectIsPlaying, selectCurrentlyAtInSeconds, setIsPlaying, selectIsMuted, selectVolume,
selectVideoURL, selectVideoCount, selectDurationInSeconds,
setPreviewTriggered, selectPreviewTriggered, setAspectRatio, selectAspectRatio, setClickTriggered, selectClickTriggered, setCurrentlyAt
} from '../redux/videoSlice'
Expand Down Expand Up @@ -53,6 +53,8 @@ const VideoPlayers: React.FC<{refs: any, widthInPercent?: number}> = ({refs, wid
first={i === 0}
last={i === videoCount - 1}
selectIsPlaying={selectIsPlaying}
selectIsMuted={selectIsMuted}
selectVolume={selectVolume}
selectCurrentlyAtInSeconds={selectCurrentlyAtInSeconds}
selectPreviewTriggered={selectPreviewTriggered}
selectClickTriggered={selectClickTriggered}
Expand Down Expand Up @@ -91,6 +93,8 @@ export const VideoPlayer = React.forwardRef(
first: boolean,
last: boolean,
selectIsPlaying:(state: RootState) => boolean,
selectIsMuted:(state: RootState) => boolean,
selectVolume:(state: RootState) => number,
selectCurrentlyAtInSeconds: (state: RootState) => number,
selectPreviewTriggered:(state: RootState) => boolean,
selectClickTriggered:(state: RootState) => boolean,
Expand All @@ -108,6 +112,8 @@ export const VideoPlayer = React.forwardRef(
url,
isPrimary,
selectIsPlaying,
selectIsMuted,
selectVolume,
subtitleUrl,
first,
last,
Expand All @@ -127,6 +133,8 @@ export const VideoPlayer = React.forwardRef(
// Init redux variables
const dispatch = useDispatch();
const isPlaying = useSelector(selectIsPlaying)
const isMuted = useSelector(selectIsMuted)
const volume = useSelector(selectVolume)
const currentlyAt = useSelector(selectCurrentlyAtInSeconds)
const duration = useSelector(selectDurationInSeconds)
const previewTriggered = useSelector(selectPreviewTriggered)
Expand Down Expand Up @@ -347,7 +355,8 @@ export const VideoPlayer = React.forwardRef(
width="unset"
height="unset"
playing={isPlaying}
muted={!isPrimary}
volume={volume}
muted={!isPrimary || isMuted}
onProgress={onProgressCallback}
progressInterval={100}
onReady={onReadyCallback}
Expand Down
16 changes: 15 additions & 1 deletion src/redux/videoSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { settings } from '../config';
export interface video {
isPlaying: boolean, // Are videos currently playing?
isPlayPreview: boolean, // Should deleted segments be skipped?
isMuted: boolean, // Is the volume muted?
volume: number, // Video playback volume
previewTriggered: boolean, // Basically acts as a callback for the video players.
clickTriggered: boolean, // Another video player callback
currentlyAt: number, // Position in the video in milliseconds
Expand All @@ -32,6 +34,8 @@ export interface video {
export const initialState: video & httpRequestState = {
isPlaying: false,
isPlayPreview: true,
isMuted: false,
volume: 1,
currentlyAt: 0, // Position in the video in milliseconds
segments: [{id: nanoid(), start: 0, end: 1, deleted: false}],
tracks: [],
Expand Down Expand Up @@ -105,6 +109,12 @@ const videoSlice = createSlice({
setIsPlayPreview: (state, action: PayloadAction<video["isPlaying"]>) => {
state.isPlayPreview = action.payload;
},
setIsMuted: (state, action: PayloadAction<video["isMuted"]>) => {
state.isMuted = action.payload;
},
setVolume: (state, action: PayloadAction<video["volume"]>) => {
state.volume = action.payload;
},
setPreviewTriggered: (state, action) => {
state.previewTriggered = action.payload
},
Expand Down Expand Up @@ -348,7 +358,7 @@ const setThumbnailHelper = (state: video, id: Track["id"], uri: Track["thumbnail
}
}

export const { setTrackEnabled, setIsPlaying, setIsPlayPreview, setCurrentlyAt, setCurrentlyAtInSeconds,
export const { setTrackEnabled, setIsPlaying, setIsPlayPreview, setIsMuted, setVolume, setCurrentlyAt, setCurrentlyAtInSeconds,
addSegment, setAspectRatio, setHasChanges, setWaveformImages, setThumbnails, setThumbnail, removeThumbnail,
cut, markAsDeletedOrAlive, setSelectedWorkflowIndex, mergeLeft, mergeRight, mergeAll, setPreviewTriggered,
setClickTriggered } = videoSlice.actions
Expand All @@ -359,6 +369,10 @@ export const selectIsPlaying = (state: { videoState: { isPlaying: video["isPlayi
state.videoState.isPlaying
export const selectIsPlayPreview = (state: { videoState: { isPlayPreview: video["isPlayPreview"] }; }) =>
state.videoState.isPlayPreview
export const selectIsMuted = (state: { videoState: { isMuted: video["isMuted"] }; }) =>
state.videoState.isMuted
export const selectVolume = (state: { videoState: { volume: video["volume"] }; }) =>
state.videoState.volume
export const selectPreviewTriggered = (state: { videoState: { previewTriggered: video["previewTriggered"] } }) =>
state.videoState.previewTriggered
export const selectClickTriggered = (state: { videoState: { clickTriggered: video["clickTriggered"] } }) =>
Expand Down
Loading

0 comments on commit 4f28fb1

Please sign in to comment.