Skip to content

Commit

Permalink
add clips section below video if video has clips
Browse files Browse the repository at this point in the history
  • Loading branch information
Zibbp committed Dec 16, 2024
1 parent f118aff commit 36d76dd
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 71 deletions.
68 changes: 0 additions & 68 deletions frontend/app/components/videos/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,74 +112,6 @@ const VideoTitleBar = ({ video }: Params) => {
</div>
</div>
</div>

// <Box className={classes.titleBarContainer}>
// <div className={classes.titleBar}>
// <Group
// justify="space-between"
// align="center"
// style={{ width: '100%' }}
// px="md"
// gap="xl"
// >
// {/* Left side - Channel info */}
// <Group align="center" wrap="nowrap" style={{ flex: '1 1 auto', minWidth: 0 }}>
// <Avatar
// src={`${env('CDN_URL')}${escapeURL(video.edges.channel.image_path)}`}
// radius="xl"
// alt={video.edges.channel.display_name}
// />
// <Divider size="sm" orientation="vertical" />
// <div style={{ minWidth: 0, flex: 1 }}>
// <Tooltip label={video.title} openDelay={250}>
// <Text size="xl" lineClamp={1}>
// {video.title}
// </Text>
// </Tooltip>
// </div>
// </Group>

// {/* Right side - Metrics */}
// <Group align="center" wrap="nowrap" gap="md" style={{ flex: '0 0 auto' }}>
// {video.views && (
// <MetricBadge
// value={video.views}
// icon={IconUsers}
// tooltip={`${video.views.toLocaleString()} source views`}
// />
// )}

// {video.local_views && (
// <MetricBadge
// value={video.local_views}
// icon={IconUser}
// tooltip={`${video.local_views.toLocaleString()} local views`}
// />
// )}

// <Tooltip label={`Originally streamed at ${video.streamed_at}`} openDelay={250}>
// <div className={classes.titleBarMetric}>
// <Text mr={5}>
// {dayjs(video.streamed_at).format("YYYY/MM/DD")}
// </Text>
// <IconCalendarEvent size={20} />
// </div>
// </Tooltip>

// <Tooltip label="Video Type" openDelay={250}>
// <Badge variant="default">
// {video.type}
// </Badge>
// </Tooltip>

// {hasPermission(UserRole.Archiver) && (
// <VideoMenu />
// )}

// </Group>
// </Group>
// </div>
// </Box>
);
};

Expand Down
38 changes: 38 additions & 0 deletions frontend/app/components/videos/VideoClips.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Video } from "@/app/hooks/useVideos";
import { Carousel } from "@mantine/carousel";
import { Title } from "@mantine/core";
import VideoCard from "./Card";

interface Params {
clips: Video[];
}

const VideoPageClips = ({ clips }: Params) => {

return (
<div>
<Title my={5}>Video Clips</Title>
<Carousel
withIndicators
slideSize={{ base: '100%', sm: '50%', md: '33.333333%', lg: '25%', xl: '16.666666%' }}
slideGap={{ base: 0, sm: 'md' }}
loop
align="start"
>
{clips.map((video) => (
<Carousel.Slide key={video.id}>
<VideoCard
key={video.id}
video={video}
showChannel={false}
showMenu={true}
showProgress={true}
/>
</Carousel.Slide>
))}
</Carousel>
</div>
);
}

export default VideoPageClips;
15 changes: 15 additions & 0 deletions frontend/app/hooks/useVideos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,20 @@ const useGetVideoByExternalId = (extId?: string) => {
});
};

const getVideoClips = async (id: string): Promise<Video[]> => {
const response = await useAxios.get<ApiResponse<Array<Video>>>(
`/api/v1/vod/${id}/clips`
);
return response.data.data;
};

const useGetVideoClips = (id: string) => {
return useQuery({
queryKey: ["video_clips", id],
queryFn: () => getVideoClips(id),
});
};

export {
fetchVideosFilter,
useFetchVideosFilter,
Expand All @@ -464,4 +478,5 @@ export {
useGenerateStaticThumbnail,
useSearchVideos,
useGetVideoByExternalId,
useGetVideoClips,
};
7 changes: 7 additions & 0 deletions frontend/app/videos/[id]/VideoPage.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,10 @@
height: 100vh;
max-height: 100vh;
}

/* I don't like hiding the scrollbar but having it visible ruins the chat player, even a custom small one */
html::-webkit-scrollbar {
display: none;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
23 changes: 20 additions & 3 deletions frontend/app/videos/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client"
import { useFetchVideo } from "@/app/hooks/useVideos";
import { useFetchVideo, useGetVideoClips } from "@/app/hooks/useVideos";
import React, { useEffect } from "react";
import classes from "./VideoPage.module.css"
import { Box } from "@mantine/core";
import { Box, Container } from "@mantine/core";
import VideoPlayer from "@/app/components/videos/Player";
import VideoTitleBar from "@/app/components/videos/TitleBar";
import ChatPlayer from "@/app/components/videos/ChatPlayer";
Expand All @@ -12,6 +12,7 @@ import { useFullscreen } from "@mantine/hooks";
import { env } from "next-runtime-env";
import VideoLoginRequired from "@/app/components/videos/LoginRequired";
import useAuthStore from "@/app/store/useAuthStore";
import VideoPageClips from "@/app/components/videos/VideoClips";

interface Params {
id: string;
Expand All @@ -27,6 +28,10 @@ const VideoPage = ({ params }: { params: Promise<Params> }) => {

const { data, isPending, isError } = useFetchVideo({ id, with_channel: true, with_chapters: true, with_muted_segments: true })

// need to fetch clips here to dynamically render the clips section
const { data: videoClips, isPending: videoClipsPending, isError: videoClipsError } = useGetVideoClips(id)


useEffect(() => {
document.title = `${data?.title}`;
}, [data?.title]);
Expand All @@ -53,7 +58,7 @@ const VideoPage = ({ params }: { params: Promise<Params> }) => {
}

return (
<div>
<div className={classes.page}>
{/* Player and chat section */}
<Box className={classes.container}>
{/* Player */}
Expand Down Expand Up @@ -84,6 +89,18 @@ const VideoPage = ({ params }: { params: Promise<Params> }) => {

{/* Title bar */}
{!videoTheaterMode && <VideoTitleBar video={data} />}


{/* Video clips */}
<Container size="7xl" fluid={true} >
{videoClipsError && (
<div>Error loading clips</div>
)}
{((!videoClipsPending) && (videoClips && videoClips.length > 0)) && (
<VideoPageClips clips={videoClips} />
)}
</Container>

</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions internal/transport/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ func groupV1Routes(e *echo.Group, h *Handler) {
vodGroup.PUT("/:id", h.UpdateVod, AuthGuardMiddleware, AuthGetUserMiddleware, AuthUserRoleMiddleware(utils.EditorRole))
vodGroup.DELETE("/:id", h.DeleteVod, AuthGuardMiddleware, AuthGetUserMiddleware, AuthUserRoleMiddleware(utils.AdminRole))
vodGroup.GET("/:id/playlist", h.GetVodPlaylists)
vodGroup.GET("/:id/clips", h.GetVodClips)
vodGroup.GET("/paginate", h.GetVodsPagination)
vodGroup.GET("/:id/chat", h.GetVodChatComments)
vodGroup.GET("/:id/chat/seek", h.GetNumberOfVodChatCommentsFromTime)
Expand Down
15 changes: 15 additions & 0 deletions internal/transport/http/vod.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type VodService interface {
GetNumberOfVodChatCommentsFromTime(c echo.Context, vodID uuid.UUID, start float64, commentCount int64) (*[]chat.Comment, error)
LockVod(c echo.Context, vID uuid.UUID, status bool) error
GenerateStaticThumbnail(ctx context.Context, videoID uuid.UUID) (*rivertype.JobInsertResult, error)
GetVodClips(ctx context.Context, id uuid.UUID) ([]*ent.Vod, error)
}

type CreateVodRequest struct {
Expand Down Expand Up @@ -655,3 +656,17 @@ func (h *Handler) GenerateStaticThumbnail(c echo.Context) error {
}
return SuccessResponse(c, nil, fmt.Sprintf("job created: %d", job.Job.ID))
}

func (h *Handler) GetVodClips(c echo.Context) error {
id := c.Param("id")
videoId, err := uuid.Parse(id)
if err != nil {
return ErrorResponse(c, http.StatusBadRequest, "Invalid video ID")
}
clips, err := h.Service.VodService.GetVodClips(c.Request().Context(), videoId)
if err != nil {
return ErrorResponse(c, http.StatusInternalServerError, err.Error())
}

return SuccessResponse(c, clips, "clips for video")
}
13 changes: 13 additions & 0 deletions internal/vod/vod.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,19 @@ func (s *Service) GenerateStaticThumbnail(ctx context.Context, videoID uuid.UUID
}, nil)
}

func (s *Service) GetVodClips(ctx context.Context, id uuid.UUID) ([]*ent.Vod, error) {
video, err := s.Store.Client.Vod.Query().Where(vod.ID(id)).Only(ctx)
if err != nil {
return nil, err
}

clips, err := s.Store.Client.Vod.Query().Where(vod.ClipExtVodID(video.ExtID)).All(ctx)
if err != nil {
return nil, err
}
return clips, nil
}

func (s *Service) GetUserIdFromChat(c echo.Context, vodID uuid.UUID) (*int64, error) {
v, err := s.Store.Client.Vod.Query().Where(vod.ID(vodID)).Only(c.Request().Context())
if err != nil {
Expand Down

0 comments on commit 36d76dd

Please sign in to comment.