diff --git a/src/components/Vod/Menu.tsx b/src/components/Vod/Menu.tsx index 18d557b..c08b120 100644 --- a/src/components/Vod/Menu.tsx +++ b/src/components/Vod/Menu.tsx @@ -18,6 +18,7 @@ import { IconTrashX, IconLock, IconLockOpen, + IconShare, } from "@tabler/icons"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; @@ -26,6 +27,7 @@ import { VodInfoModalContent } from "./InfoModalContent"; import { VodPlaylistModalContent } from "./PlaylistModalContent"; import { ROLES, useJsxAuth } from "../../hooks/useJsxAuth"; import AdminVodDelete from "../Admin/Vods/Delete"; +import vodDataBus from "./EventBus"; const useStyles = createStyles((theme) => ({ action: { @@ -74,8 +76,8 @@ export const VodMenu = ({ vod, style }: any) => { ).then(() => { queryClient.invalidateQueries(["playback-data"]); showNotification({ - title: "Als gesehen markiert", - message: "VOD wurde erfolgreich als angesehen markiert", + title: "Marked as Watched", + message: "VOD has been successfully marked as watched", }); }); }, @@ -94,8 +96,8 @@ export const VodMenu = ({ vod, style }: any) => { ).then(() => { queryClient.invalidateQueries(["vod", vod.id]); showNotification({ - title: "Video aktualisiert", - message: `Das Video wurde ${lock ? "gesperrt" : "entsperrt"}`, + title: "Video Updated", + message: `Video has been ${lock ? "locked" : "unlocked"}`, }); if (lock == true) { isLocked.current = true; @@ -120,8 +122,8 @@ export const VodMenu = ({ vod, style }: any) => { ).then(() => { queryClient.invalidateQueries(["playback-data"]); showNotification({ - title: "Als ungesehen markiert", - message: "VOD wurde erfolgreich als ungesehen markiert", + title: "Marked as Unwatched", + message: "VOD has been successfully marked as unwatched", }); }); }, @@ -135,6 +137,46 @@ export const VodMenu = ({ vod, style }: any) => { setDeletedOpened(false); }; + const shareVideo = () => { + let shareUrl: string = ""; + const url = window.location.origin; + + // check if we are on a vod page + if (window.location.pathname.includes("/vods/") && window.location.pathname.includes(vod.id)) { + // get the current time + const { time } = vodDataBus.getData(); + const roundedTime = Math.ceil(time); + // create the url + shareUrl = `${url}/vods/${vod.id}?t=${roundedTime}`; + } else { + // create the url + shareUrl = `${url}/vods/${vod.id}`; + } + + // copy the url to the clipboard + try { + navigator.clipboard.writeText(shareUrl); + + // show a notification + showNotification({ + title: "Copied to Clipboard", + message: "The video url has been copied to your clipboard", + }); + + } catch (err) { + console.error(err); + // show a notification + showNotification({ + title: "Error", + message: "The clipboard API requires HTTPS, falling back to a prompt", + color: "red", + }); + + // fallback to a prompt + prompt("Copy to clipboard: Ctrl+C, Enter", shareUrl); + } + } + return (
@@ -170,43 +212,49 @@ export const VodMenu = ({ vod, style }: any) => { onClick={() => markAsWatched.mutate()} icon={} > - Als gesehen markieren + Mark as Watched markAsUnWatched.mutate()} icon={} > - Als nicht angesehen markieren + Mark as Unwatched {isLocked.current ? ( lockVod.mutate(false)} icon={} > - Entsperren + Unlock ) : ( lockVod.mutate(true)} icon={} > - Sperren + Lock )} {useJsxAuth({ loggedIn: true, roles: [ROLES.ADMIN], }) && ( - { - openDeleteModal(); - }} - icon={} - > - Löschen - - )} + { + openDeleteModal(); + }} + icon={} + > + Delete + + )} + shareVideo()} + icon={} + > + Share + { opened={infoModalOpened} onClose={() => setInfoModalOpended(false)} size="xl" - title="VOD Informationen" + title="VOD Information" > setDeletedOpened(false)} - title="Video Löschen" + title="Delete Video" > diff --git a/src/components/Vod/VideoPlayer.tsx b/src/components/Vod/VideoPlayer.tsx index 89f1726..920629b 100644 --- a/src/components/Vod/VideoPlayer.tsx +++ b/src/components/Vod/VideoPlayer.tsx @@ -26,6 +26,8 @@ import { } from "@vidstack/react/icons"; import ReactDOM from "react-dom"; import TheaterModeIcon from "./TheaterModeIcon"; +import { escapeURL } from "../../util/util"; +import { useSearchParams } from 'next/navigation' const useStyles = createStyles((theme) => ({ playerContainer: { @@ -47,11 +49,13 @@ const NewVideoPlayer = ({ vod }: any) => { const player = useRef(null); const playerRemote = useMediaRemote(player); - const [videoSource, setVideoSource] = useState(""); + const [videoSource, setVideoSource] = useState([{ src: "", type: "" }]); const [videoType, setVideoType] = useState(""); const [videoPoster, setVideoPoster] = useState(""); const [videoTitle, setVideoTitle] = useState(""); + const searchParams = useSearchParams() + // Fetch playback data const { data } = useQuery({ refetchOnWindowFocus: false, @@ -90,13 +94,20 @@ const NewVideoPlayer = ({ vod }: any) => { type = "application/x-mpegURL"; } - setVideoSource(`${publicRuntimeConfig.CDN_URL}${vod.video_path}`); + setVideoSource([ + { + src: `${publicRuntimeConfig.CDN_URL}${escapeURL(vod.video_path)}`, + type: type, + }, + ]); setVideoType(type); setVideoTitle(vod.title); // If thumbnail if (vod.thumbnail_path) { - setVideoPoster(`${publicRuntimeConfig.CDN_URL}${vod.thumbnail_path}`); + setVideoPoster( + `${publicRuntimeConfig.CDN_URL}${escapeURL(vod.video_path)}` + ); } // If captions @@ -120,6 +131,12 @@ const NewVideoPlayer = ({ vod }: any) => { player.current!.currentTime = data.time; } + // Check if time is set in the url + const time = searchParams.get("t"); + if (time) { + player.current!.currentTime = parseInt(time); + } + const mediaFullscreenButton = document.querySelector("media-menu"); const buttonContainer = document.createElement("div");