Skip to content

Commit

Permalink
Merge pull request #322 from AppQuality/add-cutting-edge-functionalit…
Browse files Browse the repository at this point in the history
…ies-to-player

Add-cutting-edge-functionalities-to-player
  • Loading branch information
cannarocks authored Apr 29, 2024
2 parents 1e6dd50 + 1769c62 commit 70b6857
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 57 deletions.
11 changes: 11 additions & 0 deletions src/stories/player/_types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ export interface PlayerArgs extends HTMLAttributes<HTMLVideoElement> {
url: string;
start?: number;
end?: number;
enablePipOnScroll?: boolean;
onCutHandler?: (time: number) => void;
bookmarks?: IBookmark[];
}

export interface IBookmark {
start: number;
end?: number;
hue?: string;
label?: string;
onClick?: () => void;
}

export interface WrapperProps {
Expand Down
67 changes: 62 additions & 5 deletions src/stories/player/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { StoryFn, Meta } from "@storybook/react";
import { Meta, StoryFn } from "@storybook/react";
import { useCallback, useState } from "react";
import styled from "styled-components";
import { Player } from ".";
import { PlayerArgs } from "./_types";
import { IBookmark, PlayerArgs } from "./_types";
import { theme } from "../theme";

const Container = styled.div`
height: 80vh;
`;

interface PlayerStoryArgs extends PlayerArgs {
url: string;
}
interface PlayerStoryArgs extends PlayerArgs {}

const defaultArgs: PlayerStoryArgs = {
// url: "https://s3.eu-west-1.amazonaws.com/tryber.media.production/media/T6631/CP4462/bugs/bf2ed159c4c8024a82116a5dfa26ef434180db334304e0372a531592040452e4.mp4",
url: "https://s3-eu-west-1.amazonaws.com/appq.use-case-media/CP3461/UC8794/T19095/ebf00412a1bc3fd33fddd52962cf80e6853a10d5_1625090207.mp4",
onCutHandler: undefined, // Storybook fix https://github.com/storybookjs/storybook/issues/22930
};

const Template: StoryFn<PlayerStoryArgs> = (args) => (
Expand All @@ -22,6 +23,50 @@ const Template: StoryFn<PlayerStoryArgs> = (args) => (
</Container>
);

const TemplateWithCutter: StoryFn<PlayerStoryArgs> = ({
bookmarks,
...args
}) => {
const [observations, setObservations] = useState<IBookmark[]>(
bookmarks || []
);
const [start, setStart] = useState<number | undefined>(undefined);

console.log("🚀 ~ observations:", observations);
const handleCut = useCallback(
(time: number) => {
if (!start) {
setStart(time);
return;
}

setObservations([
...observations,
{
start,
end: time,
hue: theme.colors.neutralHue,
label: "New observation",
},
]);
setStart(undefined);
},
[observations, start]
);
return (
<Container id="player.story.container">
<Player {...args} onCutHandler={handleCut} bookmarks={observations} />
{start && (
<div>
Click again to set the end time for observation
<br />
Start: {start}
</div>
)}
</Container>
);
};

export const Basic = Template.bind({});
Basic.args = {
...defaultArgs,
Expand All @@ -35,6 +80,18 @@ Streaming.args = {
end: 20,
};

export const WithBookmarks = TemplateWithCutter.bind({});
WithBookmarks.args = {
...defaultArgs,
bookmarks: [
{ start: 10, hue: theme.colors.dangerHue, label: "10s" },
{ start: 20, hue: theme.colors.foreground, label: "20s" },
{ start: 30, hue: theme.colors.successHue, label: "30s" },
{ start: 40, hue: theme.colors.dangerHue, label: "40s" },
{ start: 50, hue: theme.colors.accentHue, label: "50s" },
],
};

export default {
title: "Organisms/Player",
component: Player,
Expand Down
20 changes: 9 additions & 11 deletions src/stories/player/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import Video, { useVideoContext } from "@appquality/stream-player";
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
import { PlayerArgs } from "./_types";
import { Container } from "./parts/container";
import { Controls } from "./parts/controls";
import { FloatingControls } from "./parts/floatingControls";
import { VideoSpinner } from "./parts/spinner";
import { PlayerArgs } from "./_types";

/**
* The Player is a styled media tag with custom controls
Expand All @@ -34,6 +27,7 @@ const Player = forwardRef<HTMLVideoElement, PlayerArgs>((props, forwardRef) => {
const PlayerCore = forwardRef<HTMLVideoElement, PlayerArgs>(
(props, forwardRef) => {
const { context, togglePlay, setIsPlaying } = useVideoContext();
const { onCutHandler, bookmarks } = props;
const videoRef = context.player?.ref.current;
const isLoaded = !!videoRef;
const containerRef = useRef<HTMLDivElement>(null);
Expand All @@ -54,7 +48,7 @@ const PlayerCore = forwardRef<HTMLVideoElement, PlayerArgs>(
});
}
};
}, [videoRef]);
}, [setIsPlaying, videoRef]);

return (
<Container
Expand All @@ -71,7 +65,11 @@ const PlayerCore = forwardRef<HTMLVideoElement, PlayerArgs>(
/>
)}
<Video.Player className="player-container" />
<Controls container={containerRef.current} />
<Controls
container={containerRef.current}
onCutHandler={onCutHandler}
bookmarks={bookmarks}
/>
</Container>
);
}
Expand Down
18 changes: 18 additions & 0 deletions src/stories/player/parts/bookmark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { styled } from "styled-components";
import { IBookmark } from "../_types";
import { Tooltip } from "../../tooltip";

const Rect = styled.div<{ hue?: string }>`
position: absolute;
width: 5px;
height: 100%;
background-color: ${({ hue, theme }) => hue || theme.palette.grey[800]};
transform: translateX(-50%);
z-index: 1;
`;

export const Bookmark = ({ start, hue, label, ...props }: IBookmark) => (
<Tooltip content={label} type={"light"} size={"large"}>
<Rect hue={hue} style={{ left: `${start}%` }} />
</Tooltip>
);
75 changes: 45 additions & 30 deletions src/stories/player/parts/controls.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { useVideoContext } from "@appquality/stream-player";
import { getColor } from "@zendeskgarden/react-theming";
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
import styled from "styled-components";
import { useVideoContext } from "@appquality/stream-player";
import { Progress } from "../../loaders/progress";
import { PlayerTooltip } from "./tooltip";
import { WrapperProps } from "../_types";
import { IBookmark, WrapperProps } from "../_types";
import { AudioButton } from "./audioButton";
import { ControlsGroupCenter } from "./controlsCenterGroup";
import { FullScreenButton } from "./fullScreenButton";
import { TimeLabel } from "./timeLabel";
import { AudioButton } from "./audioButton";
import { PlayerTooltip } from "./tooltip";
import { formatDuration } from "./utils";
import { FullScreenButton } from "./fullScreenButton";
import { getColor } from "@zendeskgarden/react-theming";
import { Bookmark } from "./bookmark";

export const ControlsWrapper = styled.div<WrapperProps>`
position: absolute;
Expand Down Expand Up @@ -57,8 +58,12 @@ const StyledDiv = styled.div`

export const Controls = ({
container,
onCutHandler,
bookmarks,
}: {
container: HTMLDivElement | null;
onCutHandler?: (time: number) => void;
bookmarks?: IBookmark[];
}) => {
const [progress, setProgress] = useState<number>(0);
const [tooltipMargin, setTooltipMargin] = useState<number>(0);
Expand All @@ -72,25 +77,39 @@ export const Controls = ({
const duration =
context.part.end - context.part.start || context.player?.totalTime || 0; //relative

const getVideoPositionFromEvent = (clientX: number) => {
if (progressRef && progressRef.current && duration) {
const bounds = progressRef.current.getBoundingClientRect();
const x = clientX - bounds.left;
const videoPositionSecs =
(x / progressRef.current.clientWidth) * duration;
return videoPositionSecs;
}
const getVideoPositionFromEvent = useCallback(
(clientX: number) => {
if (progressRef && progressRef.current && duration) {
const bounds = progressRef.current.getBoundingClientRect();
const x = clientX - bounds.left;
const videoPositionSecs =
(x / progressRef.current.clientWidth) * duration;
return videoPositionSecs;
}

return 0;
},
[progressRef, duration]
);

return 0;
};
const getProgress = useCallback(
(currentTime: number) => {
const current = currentTime - (context.part.start || 0);

if (duration === 0) return 0;

return (current / duration) * 100;
},
[context.part.start, duration]
);

const handleSkipAhead = useCallback(
(pageX: number) => {
const time = getVideoPositionFromEvent(pageX) + (context.part.start || 0);
setCurrentTime(time);
setProgress(getProgress(time));
},
[context.player, context.part]
[getVideoPositionFromEvent, context.part.start, setCurrentTime, getProgress]
);

const onMouseEvent = (e: MouseEvent<HTMLDivElement>) => {
Expand All @@ -111,18 +130,7 @@ export const Controls = ({
useEffect(() => {
const currentTime = context.player?.currentTime || 0;
setProgress(getProgress(currentTime));
}, [context.player]);

const getProgress = useCallback(
(currentTime: number) => {
const current = currentTime - (context.part.start || 0);

if (duration === 0) return 0;

return (current / duration) * 100;
},
[context.player]
);
}, [context.player, getProgress]);

return (
<ControlsWrapper isPlaying={context.isPlaying}>
Expand All @@ -138,6 +146,13 @@ export const Controls = ({
current={formatDuration(relCurrentTime)}
duration={formatDuration(duration)}
/>
{!!duration && bookmarks?.map((bookmark, index) => (
<Bookmark
key={index}
{...bookmark}
start={(bookmark.start / duration) * 100}
/>
))}
<StyledProgress
ref={progressRef}
value={progress}
Expand All @@ -148,7 +163,7 @@ export const Controls = ({
<StyledDiv>
<AudioButton />
</StyledDiv>
<ControlsGroupCenter />
<ControlsGroupCenter onCutHandler={onCutHandler} />

<StyledDiv>
<FullScreenButton container={container} />
Expand Down
31 changes: 24 additions & 7 deletions src/stories/player/parts/controlsCenterGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { ReactComponent as PlayIcon } from "../../../assets/icons/play-fill.svg";
import { ReactComponent as PauseIcon } from "../../../assets/icons/pause-fill.svg";
import { ReactComponent as ForwardIcon } from "../../../assets/icons/forward-seconds-fill.svg";
import { useVideoContext } from "@appquality/stream-player";
import { useEffect, useState } from "react";
import styled from "styled-components";
import { ReactComponent as RewindIcon } from "../../../assets/icons/back-seconds-fill.svg";
import { ReactComponent as ForwardIcon } from "../../../assets/icons/forward-seconds-fill.svg";
import { ReactComponent as PauseIcon } from "../../../assets/icons/pause-fill.svg";
import { ReactComponent as PlayIcon } from "../../../assets/icons/play-fill.svg";
import { ReactComponent as PreviousIcon } from "../../../assets/icons/previous-fill.svg";
import styled from "styled-components";
import { IconButton } from "../../buttons/icon-button";
import { SM } from "../../typography/typescale";
import { getNextPlaybackRate } from "./utils";
import { useCallback, useEffect, useState } from "react";
import { useVideoContext } from "@appquality/stream-player";

const StyledDiv = styled.div`
display: flex;
align-items: center;
`;

export const ControlsGroupCenter = () => {
export const ControlsGroupCenter = ({
onCutHandler,
}: {
onCutHandler?: (time: number) => void;
}) => {
const [playBackRate, setPlayBackRate] = useState<number>(1);
const { context, togglePlay } = useVideoContext();

Expand Down Expand Up @@ -95,6 +99,19 @@ export const ControlsGroupCenter = () => {
{playBackRate}x
</SM>
</IconButton>
{onCutHandler && (
<IconButton
isBright
onClick={(e) => {
if (videoRef) {
onCutHandler(videoRef.currentTime);
}
e.stopPropagation();
}}
>
<SM isBold>✂️</SM>
</IconButton>
)}
</StyledDiv>
);
};
4 changes: 3 additions & 1 deletion src/stories/slider/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ const Template: Story<SliderStoryArg> = ({ items, ...args }) => {
{items.map((item) => (
<div>
<Slider.Slide>
{item.imageUrl && <img src={item.imageUrl} />}
{item.imageUrl && (
<img src={item.imageUrl} alt={item.headerTitle} />
)}
{item.videoUrl && <Player url={item.videoUrl} />}
{item.headerTitle && item.content && (
<TextContainer>
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5503,9 +5503,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464:
version "1.0.30001579"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz"
integrity sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==
version "1.0.30001614"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz"
integrity sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog==

case-anything@^2.1.10:
version "2.1.11"
Expand Down

0 comments on commit 70b6857

Please sign in to comment.