Skip to content

Commit

Permalink
Merge pull request #8 from red-ninjas/video-component
Browse files Browse the repository at this point in the history
Video component
  • Loading branch information
dev-ABsid authored Nov 1, 2023
2 parents fafe7fd + 6846e8d commit 39568f3
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 2 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@himalaya-ui/core",
"version": "0.0.57",
"version": "0.0.58",
"main": "dist/bundle/index.js",
"module": "dist/bundle/esm/index.js",
"types": "dist/bundle/esm/index.d.ts",
Expand Down
6 changes: 6 additions & 0 deletions src/app/components/video/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client';
import Documentation from './video.mdx';

export default function Page() {
return <Documentation />;
}
37 changes: 37 additions & 0 deletions src/app/components/video/video.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Layout, Playground, Attributes } from 'lib/components'
import { Video } from 'components'

export const meta = {
title: 'Video',
group: 'General',
}

# Video

Display video content.

<Playground
scope={{ Video }}
code={`
<Video
src="https://media.w3.org/2010/05/sintel/trailer_hd.mp4"
width={2000 / 3.5}
height={1300 / 2.7}
controls={true}
autoplay={true}
muted={true}
/>
`}
/>
<Attributes edit="/pages/components/video.mdx">
<Attributes.Title>Video.Props</Attributes.Title>

| Attribute | Description | Type | Accepted Values | Default |
| ------------ | --------------------------------------- | --------- | -------------------------- | ------- |
| **src** | The source URL of the video. | `string` | - | - |
| **width** | The width of the video player. | `number` | - | - |
| **height** | The height of the video player. | `number` | - | - |
| **controls** | Determines if video controls are shown. | `boolean` | `false` (to hide controls) | `true` |
| **autoplay** | Determines if the video should autoplay. | `boolean` | `true` (to autoplay) | `false` |
| **muted** | Determines if the video should be muted. | `boolean` | `true` (to mute) | `false` |
</Attributes>
3 changes: 3 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ export { default as BottomNavigation } from './bottom-navigation';
export { default as ShowMore } from './show-more';
export type { ShowMoreProps } from './show-more';

export { default as Video } from './video';
export type { VideoProps } from './video';

export { default as RunningText } from './running-text';
export { default as Footer, FooterBottom, FooterNavigation } from './footer';

Expand Down
4 changes: 4 additions & 0 deletions src/components/video/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Video from './video';

export type { VideoProps } from './video';
export default Video;
201 changes: 201 additions & 0 deletions src/components/video/video.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
'use client';
import React, { useRef, useState } from 'react';
import { PlayFill, PauseFill, Minimize, Maximize, Volume2, VolumeX } from '../icons';
import useTheme from '../use-theme';

interface Props {
src: string;
width: number;
height: number;
controls: boolean;
autoplay?: boolean;
muted?: boolean;
}

type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>;
export type VideoProps = Props & NativeAttrs;

const Video: React.FC<VideoProps> = ({ src, width, height, controls, autoplay = false, muted = false }) => {
const theme = useTheme();

const [isPlaying, setIsPlaying] = useState(autoplay);
const [isMuted, setIsMuted] = useState(muted);
const [isFullScreen, setIsFullScreen] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);

const videoRef = useRef<HTMLVideoElement>(null);

const handletoggleMute = () => {
if (videoRef.current) {
videoRef.current.muted = !isMuted;
setIsMuted(!isMuted);
}
};

const handleFullScreen = () => {
if (!isFullScreen) {
if (videoRef.current) {
videoRef.current.requestFullscreen();
}
} else {
setIsFullScreen(!isFullScreen);
}
};

const formatTime = seconds => {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);

const formattedMinutes = String(minutes).padStart(2, '0'); // Ensure two-digit format.
const formattedSeconds = String(remainingSeconds).padStart(2, '0'); // Ensure two-digit format.

return `${formattedMinutes}:${formattedSeconds}`;
};

const handlePlayPause = () => {
if (isPlaying) {
videoRef?.current?.pause();
setIsPlaying(false);
} else {
videoRef?.current?.play();
setIsPlaying(true);
}
};

const handleSeek = e => {
const seekTime = (e.nativeEvent.offsetX / e.target.clientWidth) * duration;
if (videoRef.current) {
videoRef.current.currentTime = seekTime;
}
};

return (
<div className="video-player">
<div className="video-container">
<video
ref={videoRef}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
autoPlay={autoplay}
muted={muted}
width={width}
height={height}
onClick={() => {
if (!controls) {
if (isPlaying) {
videoRef.current?.pause();
} else {
videoRef.current?.play();
}
setIsPlaying(!isPlaying);
}
}}
onTimeUpdate={() => {
if (videoRef.current) {
setCurrentTime(videoRef.current.currentTime);
}
}}
onLoadedMetadata={() => {
if (videoRef.current) {
setDuration(videoRef.current.duration);
}
}}
>
<source src={src} type="video/mp4" />
</video>
{controls && (
<div className="controls">
<button onClick={handlePlayPause}>{isPlaying ? <PauseFill /> : <PlayFill />}</button>
<div className="video-info">
<span>{formatTime(currentTime)}</span>
</div>
<div className="progress-bar" onClick={handleSeek}>
<div className="progress" style={{ width: `${(currentTime / duration) * 100}%` }}></div>
</div>
<span>{formatTime(duration)}</span>
<button onClick={handletoggleMute}>{isMuted ? <VolumeX /> : <Volume2 />}</button>
<button onClick={handleFullScreen}>{isFullScreen ? <Minimize /> : <Maximize />}</button>
</div>
)}
</div>

<style jsx>{`
.video-player {
position: relative;
max-width: 100%;
margin: 0 auto;
width: ${width};
height: ${height};
}
.video-player video {
width: 100%;
cursor: ${controls ? 'default' : 'pointer'};
}
.video-player .video-container {
display: flex;
justify-content: center;
}
.controls {
position: absolute;
bottom: 5%;
background-color: ${theme.palette.background};
height: 56px;
display: flex;
opacity: 0;
align-items: center;
justify-content: center;
padding: 0 8px;
border-radius: 6px;
width: 85%;
transform: translate3d(0, 6px, 0);
transition: all 0.2s cubic-bezier(0.25, 0.57, 0.45, 0.94);
}
.video-player:hover .controls {
opacity: 1;
transform: translateZ(0);
display: flex;
}
.controls .progress-bar {
background-color: ${theme.palette.accents_6};
height: 4px;
cursor: pointer;
width: 100%;
margin-right: 8px;
}
.controls .progress {
background-color: #007bff;
height: 100%;
transition: width 0.1s linear;
}
.controls button,
.controls span {
color: ${theme.palette.foreground};
font-size: 16px;
margin-right: 10px;
border: none;
background-color: transparent;
cursor: pointer;
outline: none;
}
.controls button:hover,
.controls input[type='range']:hover {
opacity: 0.8;
}
.controls button:active,
.controls input[type='range']:active {
opacity: 0.6;
}
.controls button.active {
color: #00ccff;
}
.controls span {
font-weight: bold;
}
`}</style>
</div>
);
};

Video.displayName = 'HimalayaVideo';
export default Video;
Loading

0 comments on commit 39568f3

Please sign in to comment.