forked from Qortal/q-blog
Add video cover images
This commit is contained in:
953
BlogIndividualPost.tsx
Normal file
953
BlogIndividualPost.tsx
Normal file
@@ -0,0 +1,953 @@
|
|||||||
|
import React, { useMemo, useRef, useState } from 'react'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
CardHeader,
|
||||||
|
Avatar,
|
||||||
|
useTheme,
|
||||||
|
Tooltip
|
||||||
|
} from '@mui/material'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { styled } from '@mui/system'
|
||||||
|
import AudiotrackIcon from '@mui/icons-material/Audiotrack'
|
||||||
|
import ReadOnlySlate from '../../components/editor/ReadOnlySlate'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { RootState } from '../../state/store'
|
||||||
|
import { checkStructure } from '../../utils/checkStructure'
|
||||||
|
import { BlogContent } from '../../interfaces/interfaces'
|
||||||
|
import ShareIcon from '@mui/icons-material/Share'
|
||||||
|
import {
|
||||||
|
setAudio,
|
||||||
|
setCurrAudio,
|
||||||
|
setIsLoadingGlobal,
|
||||||
|
setVisitingBlog
|
||||||
|
} from '../../state/features/globalSlice'
|
||||||
|
import { VideoPlayer } from '../../components/common/VideoPlayer'
|
||||||
|
import { AudioPlayer, IPlaylist } from '../../components/common/AudioPlayer'
|
||||||
|
import { Responsive, WidthProvider } from 'react-grid-layout'
|
||||||
|
import '/node_modules/react-grid-layout/css/styles.css'
|
||||||
|
import '/node_modules/react-resizable/css/styles.css'
|
||||||
|
import DynamicHeightItem from '../../components/DynamicHeightItem'
|
||||||
|
import {
|
||||||
|
addPrefix,
|
||||||
|
buildIdentifierFromCreateTitleIdAndId,
|
||||||
|
removePrefix
|
||||||
|
} from '../../utils/blogIdformats'
|
||||||
|
import { DynamicHeightItemMinimal } from '../../components/DynamicHeightItemMinimal'
|
||||||
|
import { ReusableModal } from '../../components/modals/ReusableModal'
|
||||||
|
import AudioElement from '../../components/AudioElement'
|
||||||
|
import ErrorBoundary from '../../components/common/ErrorBoundary'
|
||||||
|
import { CommentSection } from '../../components/common/Comments/CommentSection'
|
||||||
|
import { Tipping } from '../../components/common/Tipping/Tipping'
|
||||||
|
import FileElement from '../../components/FileElement'
|
||||||
|
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||||
|
import { setNotification } from '../../state/features/notificationsSlice'
|
||||||
|
import ContextMenuResource from '../../components/common/ContextMenu/ContextMenuResource'
|
||||||
|
|
||||||
|
const ResponsiveGridLayout = WidthProvider(Responsive)
|
||||||
|
const initialMinHeight = 2 // Define an initial minimum height for grid items
|
||||||
|
|
||||||
|
const md = [
|
||||||
|
{ i: 'a', x: 0, y: 0, w: 4, h: initialMinHeight },
|
||||||
|
{ i: 'b', x: 6, y: 0, w: 4, h: initialMinHeight }
|
||||||
|
]
|
||||||
|
const sm = [
|
||||||
|
{ i: 'a', x: 0, y: 0, w: 6, h: initialMinHeight },
|
||||||
|
{ i: 'b', x: 6, y: 0, w: 6, h: initialMinHeight }
|
||||||
|
]
|
||||||
|
const xs = [
|
||||||
|
{ i: 'a', x: 0, y: 0, w: 6, h: initialMinHeight },
|
||||||
|
{ i: 'b', x: 6, y: 0, w: 6, h: initialMinHeight }
|
||||||
|
]
|
||||||
|
|
||||||
|
interface ILayoutGeneralSettings {
|
||||||
|
padding: number
|
||||||
|
blogPostType: string
|
||||||
|
}
|
||||||
|
export const BlogIndividualPost = () => {
|
||||||
|
const { user, postId: postIdTemp, blog:blogTemp } = useParams()
|
||||||
|
|
||||||
|
const blog = React.useMemo(()=> {
|
||||||
|
if(postIdTemp && postIdTemp?.includes('-post-')){
|
||||||
|
const str = postIdTemp
|
||||||
|
const arr = str.split('-post-')
|
||||||
|
const str1 = arr[0]
|
||||||
|
const blogId = removePrefix(str1)
|
||||||
|
return blogId
|
||||||
|
} else {
|
||||||
|
return blogTemp
|
||||||
|
}
|
||||||
|
}, [postIdTemp])
|
||||||
|
|
||||||
|
const postId = React.useMemo(()=> {
|
||||||
|
if(postIdTemp && postIdTemp?.includes('-post-')){
|
||||||
|
const str = postIdTemp
|
||||||
|
const arr = str.split('-post-')
|
||||||
|
const str2 = arr[1]
|
||||||
|
return str2
|
||||||
|
} else {
|
||||||
|
return postIdTemp
|
||||||
|
}
|
||||||
|
}, [postIdTemp])
|
||||||
|
|
||||||
|
const blogFull = React.useMemo(() => {
|
||||||
|
if (!blog) return ''
|
||||||
|
return addPrefix(blog)
|
||||||
|
}, [blog])
|
||||||
|
const { user: userState } = useSelector((state: RootState) => state.auth)
|
||||||
|
const { audios, audioPostId } = useSelector(
|
||||||
|
(state: RootState) => state.global
|
||||||
|
)
|
||||||
|
|
||||||
|
const [avatarUrl, setAvatarUrl] = React.useState<string>('')
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const theme = useTheme()
|
||||||
|
// const [currAudio, setCurrAudio] = React.useState<number | null>(null)
|
||||||
|
const [layouts, setLayouts] = React.useState<any>({ md, sm, xs })
|
||||||
|
const [count, setCount] = React.useState<number>(1)
|
||||||
|
const [layoutGeneralSettings, setLayoutGeneralSettings] =
|
||||||
|
React.useState<ILayoutGeneralSettings | null>(null)
|
||||||
|
const [currentBreakpoint, setCurrentBreakpoint] = React.useState<any>()
|
||||||
|
const handleLayoutChange = (layout: any, layoutss: any) => {
|
||||||
|
// const redoLayouts = setAutoHeight(layoutss)
|
||||||
|
setLayouts(layoutss)
|
||||||
|
// saveLayoutsToLocalStorage(layoutss)
|
||||||
|
}
|
||||||
|
const [blogContent, setBlogContent] = React.useState<BlogContent | null>(null)
|
||||||
|
const [isOpenSwitchPlaylistModal, setisOpenSwitchPlaylistModal] =
|
||||||
|
useState<boolean>(false)
|
||||||
|
const tempSaveAudio = useRef<any>(null)
|
||||||
|
const saveAudio = React.useRef<any>(null)
|
||||||
|
|
||||||
|
const fullPostId = useMemo(() => {
|
||||||
|
if (!blog || !postId) return ''
|
||||||
|
dispatch(setIsLoadingGlobal(true))
|
||||||
|
const formBlogId = addPrefix(blog)
|
||||||
|
const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId)
|
||||||
|
return formPostId
|
||||||
|
}, [blog, postId])
|
||||||
|
const getBlogPost = React.useCallback(async () => {
|
||||||
|
try {
|
||||||
|
if (!blog || !postId) return
|
||||||
|
dispatch(setIsLoadingGlobal(true))
|
||||||
|
const formBlogId = addPrefix(blog)
|
||||||
|
const formPostId = buildIdentifierFromCreateTitleIdAndId(
|
||||||
|
formBlogId,
|
||||||
|
postId
|
||||||
|
)
|
||||||
|
const url = `/arbitrary/BLOG_POST/${user}/${formPostId}`
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const responseData = await response.json()
|
||||||
|
|
||||||
|
if (checkStructure(responseData)) {
|
||||||
|
setBlogContent(responseData)
|
||||||
|
if (responseData?.layouts) {
|
||||||
|
setLayouts(responseData?.layouts)
|
||||||
|
}
|
||||||
|
if (responseData?.layoutGeneralSettings) {
|
||||||
|
setLayoutGeneralSettings(responseData.layoutGeneralSettings)
|
||||||
|
}
|
||||||
|
const filteredAudios = (responseData?.postContent || []).filter(
|
||||||
|
(content: any) => content?.type === 'audio'
|
||||||
|
)
|
||||||
|
|
||||||
|
const transformAudios = filteredAudios?.map((fa: any) => {
|
||||||
|
return {
|
||||||
|
...(fa?.content || {}),
|
||||||
|
id: fa?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!audios && transformAudios.length > 0) {
|
||||||
|
saveAudio.current = { audios: transformAudios, postId: formPostId }
|
||||||
|
dispatch(setAudio({ audios: transformAudios, postId: formPostId }))
|
||||||
|
} else if (
|
||||||
|
formPostId === audioPostId &&
|
||||||
|
audios?.length !== transformAudios.length
|
||||||
|
) {
|
||||||
|
tempSaveAudio.current = {
|
||||||
|
message:
|
||||||
|
"This post's audio playlist has updated. Would you like to switch?"
|
||||||
|
}
|
||||||
|
setisOpenSwitchPlaylistModal(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
|
dispatch(setIsLoadingGlobal(false))
|
||||||
|
}
|
||||||
|
}, [user, postId, blog])
|
||||||
|
React.useEffect(() => {
|
||||||
|
getBlogPost()
|
||||||
|
}, [postId])
|
||||||
|
|
||||||
|
const switchPlayList = () => {
|
||||||
|
const filteredAudios = (blogContent?.postContent || []).filter(
|
||||||
|
(content) => content?.type === 'audio'
|
||||||
|
)
|
||||||
|
|
||||||
|
const formatAudios = filteredAudios.map((fa) => {
|
||||||
|
return {
|
||||||
|
...(fa?.content || {}),
|
||||||
|
id: fa?.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!blog || !postId) return
|
||||||
|
const formBlogId = addPrefix(blog)
|
||||||
|
const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId)
|
||||||
|
dispatch(setAudio({ audios: formatAudios, postId: formPostId }))
|
||||||
|
if (tempSaveAudio?.current?.currentSelection) {
|
||||||
|
const findIndex = (formatAudios || []).findIndex(
|
||||||
|
(item) =>
|
||||||
|
item?.identifier ===
|
||||||
|
tempSaveAudio?.current?.currentSelection?.content?.identifier
|
||||||
|
)
|
||||||
|
if (findIndex >= 0) {
|
||||||
|
dispatch(setCurrAudio(findIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setisOpenSwitchPlaylistModal(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAvatar = React.useCallback(async () => {
|
||||||
|
try {
|
||||||
|
let url = await qortalRequest({
|
||||||
|
action: 'GET_QDN_RESOURCE_URL',
|
||||||
|
name: user,
|
||||||
|
service: 'THUMBNAIL',
|
||||||
|
identifier: 'qortal_avatar'
|
||||||
|
})
|
||||||
|
|
||||||
|
setAvatarUrl(url)
|
||||||
|
} catch (error) {}
|
||||||
|
}, [user])
|
||||||
|
React.useEffect(() => {
|
||||||
|
getAvatar()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const onBreakpointChange = React.useCallback((newBreakpoint: any) => {
|
||||||
|
setCurrentBreakpoint(newBreakpoint)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const onResizeStop = React.useCallback((layout: any, layoutItem: any) => {
|
||||||
|
// Update the layout state with the new position and size of the component
|
||||||
|
setCount((prev) => prev + 1)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// const audios = React.useMemo<IPlaylist[]>(() => {
|
||||||
|
// const filteredAudios = (blogContent?.postContent || []).filter(
|
||||||
|
// (content) => content.type === 'audio'
|
||||||
|
// )
|
||||||
|
|
||||||
|
// return filteredAudios.map((fa) => {
|
||||||
|
// return {
|
||||||
|
// ...fa.content,
|
||||||
|
// id: fa.id
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }, [blogContent])
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
setCount((prev) => prev + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleCount = React.useCallback(() => {
|
||||||
|
// Update the layout state with the new position and size of the component
|
||||||
|
setCount((prev) => prev + 1)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getBlog = React.useCallback(async () => {
|
||||||
|
let name = user
|
||||||
|
if (!name) return
|
||||||
|
if (!blogFull) return
|
||||||
|
try {
|
||||||
|
const urlBlog = `/arbitrary/BLOG/${name}/${blogFull}`
|
||||||
|
const response = await fetch(urlBlog, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const responseData = await response.json()
|
||||||
|
dispatch(setVisitingBlog({ ...responseData, name }))
|
||||||
|
} catch (error) {}
|
||||||
|
}, [user, blogFull])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
getBlog()
|
||||||
|
}, [user, blogFull])
|
||||||
|
|
||||||
|
if (!blogContent) return null
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: '1400px',
|
||||||
|
// margin: '15px',
|
||||||
|
width: '95%',
|
||||||
|
paddingBottom: '50px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user === userState?.name && (
|
||||||
|
<Button
|
||||||
|
sx={{ backgroundColor: theme.palette.secondary.main }}
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/${user}/${blog}/${postId}/edit`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit Post
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardHeader
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/${user}/${blog}`)
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
'& .MuiCardHeader-content': {
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
padding: '10px 0px'
|
||||||
|
}}
|
||||||
|
avatar={<Avatar src={avatarUrl} alt={`${user}'s avatar`} />}
|
||||||
|
subheader={
|
||||||
|
<Typography
|
||||||
|
sx={{ fontFamily: 'Cairo', fontSize: '25px' }}
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
>{` ${user}`}</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{user && (
|
||||||
|
<Tipping
|
||||||
|
name={user || ''}
|
||||||
|
onSubmit={() => {
|
||||||
|
// setNameTip('')
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
// setNameTip('')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h1"
|
||||||
|
color="textPrimary"
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{blogContent?.title}
|
||||||
|
</Typography>
|
||||||
|
<Tooltip title={`Copy post link`} arrow>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyToClipboard
|
||||||
|
text={`qortal://APP/Q-Blog/${user}/${blog}/${postId}`}
|
||||||
|
onCopy={() => {
|
||||||
|
dispatch(
|
||||||
|
setNotification({
|
||||||
|
msg: 'Copied to clipboard!',
|
||||||
|
alertType: 'success'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ShareIcon />
|
||||||
|
</CopyToClipboard>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<CommentSection postId={fullPostId} postName={user || ''} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{(layoutGeneralSettings?.blogPostType === 'builder' ||
|
||||||
|
!layoutGeneralSettings?.blogPostType) && (
|
||||||
|
<Content
|
||||||
|
layouts={layouts}
|
||||||
|
blogContent={blogContent}
|
||||||
|
onResizeStop={onResizeStop}
|
||||||
|
onBreakpointChange={onBreakpointChange}
|
||||||
|
handleLayoutChange={handleLayoutChange}
|
||||||
|
>
|
||||||
|
{blogContent?.postContent?.map((section: any) => {
|
||||||
|
if (section?.type === 'editor') {
|
||||||
|
return (
|
||||||
|
<div key={section?.id} className="grid-item-view">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItem
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={layoutGeneralSettings?.padding}
|
||||||
|
>
|
||||||
|
<ReadOnlySlate content={section.content} />
|
||||||
|
</DynamicHeightItem>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (section?.type === 'image') {
|
||||||
|
return (
|
||||||
|
<div key={section?.id} className="grid-item-view">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItem
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={layoutGeneralSettings?.padding}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={section.content.image}
|
||||||
|
className="post-image"
|
||||||
|
/>
|
||||||
|
</DynamicHeightItem>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (section?.type === 'video') {
|
||||||
|
return (
|
||||||
|
<div key={section?.id} className="grid-item-view">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItem
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={layoutGeneralSettings?.padding}
|
||||||
|
>
|
||||||
|
<ContextMenuResource
|
||||||
|
name={section.content.name}
|
||||||
|
service={section.content.service}
|
||||||
|
identifier={section.content.identifier}
|
||||||
|
link={`qortal://${section?.content?.service}/${section?.content?.name}/${section?.content?.identifier}`}
|
||||||
|
>
|
||||||
|
<VideoPlayer
|
||||||
|
name={section.content.name}
|
||||||
|
service={section.content.service}
|
||||||
|
identifier={section.content.identifier}
|
||||||
|
poster={section.content.poster}
|
||||||
|
setCount={handleCount}
|
||||||
|
user={user}
|
||||||
|
postId={fullPostId}
|
||||||
|
/>
|
||||||
|
</ContextMenuResource>
|
||||||
|
</DynamicHeightItem>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (section?.type === 'audio') {
|
||||||
|
return (
|
||||||
|
<div key={section?.id} className="grid-item-view">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItem
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={layoutGeneralSettings?.padding}
|
||||||
|
>
|
||||||
|
<ContextMenuResource
|
||||||
|
name={section.content.name}
|
||||||
|
service={section.content.service}
|
||||||
|
identifier={section.content.identifier}
|
||||||
|
link={`qortal://${section?.content?.service}/${section?.content?.name}/${section?.content?.identifier}`}
|
||||||
|
>
|
||||||
|
<AudioElement
|
||||||
|
key={section.id}
|
||||||
|
audioInfo={section.content}
|
||||||
|
postId={fullPostId}
|
||||||
|
user={user ? user : ''}
|
||||||
|
onClick={() => {
|
||||||
|
if (!blog || !postId) return
|
||||||
|
|
||||||
|
const formBlogId = addPrefix(blog)
|
||||||
|
const formPostId =
|
||||||
|
buildIdentifierFromCreateTitleIdAndId(
|
||||||
|
formBlogId,
|
||||||
|
postId
|
||||||
|
)
|
||||||
|
if (audioPostId && formPostId !== audioPostId) {
|
||||||
|
tempSaveAudio.current = {
|
||||||
|
...(tempSaveAudio.current || {}),
|
||||||
|
currentSelection: section,
|
||||||
|
message:
|
||||||
|
'You are current on a playlist. Would you like to switch?'
|
||||||
|
}
|
||||||
|
setisOpenSwitchPlaylistModal(true)
|
||||||
|
} else {
|
||||||
|
if (!audios && saveAudio?.current) {
|
||||||
|
const findIndex = (
|
||||||
|
saveAudio?.current?.audios || []
|
||||||
|
).findIndex(
|
||||||
|
(item: any) =>
|
||||||
|
item.identifier ===
|
||||||
|
section.content.identifier
|
||||||
|
)
|
||||||
|
dispatch(setAudio(saveAudio?.current))
|
||||||
|
dispatch(setCurrAudio(findIndex))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const findIndex = (audios || []).findIndex(
|
||||||
|
(item) =>
|
||||||
|
item.identifier ===
|
||||||
|
section.content.identifier
|
||||||
|
)
|
||||||
|
if (findIndex >= 0) {
|
||||||
|
dispatch(setCurrAudio(findIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title={section.content?.title}
|
||||||
|
description={section.content?.description}
|
||||||
|
author=""
|
||||||
|
/>
|
||||||
|
</ContextMenuResource>
|
||||||
|
</DynamicHeightItem>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (section?.type === 'file') {
|
||||||
|
return (
|
||||||
|
<div key={section?.id} className="grid-item">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItemMinimal
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={0}
|
||||||
|
>
|
||||||
|
<ContextMenuResource
|
||||||
|
name={section.content.name}
|
||||||
|
service={section.content.service}
|
||||||
|
identifier={section.content.identifier}
|
||||||
|
link={`qortal://${section?.content?.service}/${section?.content?.name}/${section?.content?.identifier}`}
|
||||||
|
>
|
||||||
|
<FileElement
|
||||||
|
key={section.id}
|
||||||
|
fileInfo={section.content}
|
||||||
|
postId={fullPostId}
|
||||||
|
user={user ? user : ''}
|
||||||
|
title={section.content?.title}
|
||||||
|
description={section.content?.description}
|
||||||
|
mimeType={section.content?.mimeType}
|
||||||
|
author=""
|
||||||
|
/>
|
||||||
|
</ContextMenuResource>
|
||||||
|
</DynamicHeightItemMinimal>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Content>
|
||||||
|
)}
|
||||||
|
{layoutGeneralSettings?.blogPostType === 'minimal' && (
|
||||||
|
<>
|
||||||
|
{layouts?.rows?.map((row: any, rowIndex: number) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: '25px',
|
||||||
|
gap: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row?.ids?.map((elementId: string) => {
|
||||||
|
const section: any = blogContent?.postContent?.find(
|
||||||
|
(el) => el?.id === elementId
|
||||||
|
)
|
||||||
|
if (!section) return null
|
||||||
|
if (section?.type === 'editor') {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={section?.id}
|
||||||
|
className="grid-item"
|
||||||
|
style={{
|
||||||
|
maxWidth: '800px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItemMinimal
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={0}
|
||||||
|
>
|
||||||
|
<ReadOnlySlate
|
||||||
|
key={section.id}
|
||||||
|
content={section.content}
|
||||||
|
/>
|
||||||
|
</DynamicHeightItemMinimal>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (section?.type === 'image') {
|
||||||
|
return (
|
||||||
|
<div key={section.id} className="grid-item">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItemMinimal
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
type="image"
|
||||||
|
padding={0}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={section.content.image}
|
||||||
|
className="post-image"
|
||||||
|
style={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
maxHeight: '50vh'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DynamicHeightItemMinimal>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section?.type === 'video') {
|
||||||
|
return (
|
||||||
|
<div key={section?.id} className="grid-item">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItemMinimal
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={0}
|
||||||
|
>
|
||||||
|
<ContextMenuResource
|
||||||
|
name={section.content.name}
|
||||||
|
service={section.content.service}
|
||||||
|
identifier={section.content.identifier}
|
||||||
|
link={`qortal://${section?.content?.service}/${section?.content?.name}/${section?.content?.identifier}`}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VideoPlayer
|
||||||
|
name={section.content.name}
|
||||||
|
service={section.content.service}
|
||||||
|
identifier={section.content.identifier}
|
||||||
|
poster={section.content.poster}
|
||||||
|
customStyle={{
|
||||||
|
height: '50vh'
|
||||||
|
}}
|
||||||
|
user={user}
|
||||||
|
postId={fullPostId}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</ContextMenuResource>
|
||||||
|
</DynamicHeightItemMinimal>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (section?.type === 'audio') {
|
||||||
|
return (
|
||||||
|
<div key={section?.id} className="grid-item">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItemMinimal
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={0}
|
||||||
|
>
|
||||||
|
<ContextMenuResource
|
||||||
|
name={section.content.name}
|
||||||
|
service={section.content.service}
|
||||||
|
identifier={section.content.identifier}
|
||||||
|
link={`qortal://${section?.content?.service}/${section?.content?.name}/${section?.content?.identifier}`}
|
||||||
|
>
|
||||||
|
<AudioElement
|
||||||
|
key={section.id}
|
||||||
|
audioInfo={section.content}
|
||||||
|
postId={fullPostId}
|
||||||
|
user={user ? user : ''}
|
||||||
|
onClick={() => {
|
||||||
|
if (!blog || !postId) return
|
||||||
|
const formBlogId = addPrefix(blog)
|
||||||
|
const formPostId =
|
||||||
|
buildIdentifierFromCreateTitleIdAndId(
|
||||||
|
formBlogId,
|
||||||
|
postId
|
||||||
|
)
|
||||||
|
if (formPostId !== audioPostId) {
|
||||||
|
tempSaveAudio.current = {
|
||||||
|
...(tempSaveAudio.current || {}),
|
||||||
|
currentSelection: section,
|
||||||
|
message:
|
||||||
|
'You are current on a playlist. Would you like to switch?'
|
||||||
|
}
|
||||||
|
setisOpenSwitchPlaylistModal(true)
|
||||||
|
} else {
|
||||||
|
const findIndex = (
|
||||||
|
audios || []
|
||||||
|
).findIndex(
|
||||||
|
(item) =>
|
||||||
|
item.identifier ===
|
||||||
|
section.content.identifier
|
||||||
|
)
|
||||||
|
if (findIndex >= 0) {
|
||||||
|
dispatch(setCurrAudio(findIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title={section.content?.title}
|
||||||
|
description={section.content?.description}
|
||||||
|
author=""
|
||||||
|
/>
|
||||||
|
</ContextMenuResource>
|
||||||
|
</DynamicHeightItemMinimal>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (section?.type === 'file') {
|
||||||
|
return (
|
||||||
|
<div key={section?.id} className="grid-item">
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>
|
||||||
|
Error loading content: Invalid Data
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DynamicHeightItemMinimal
|
||||||
|
layouts={layouts}
|
||||||
|
setLayouts={setLayouts}
|
||||||
|
i={section.id}
|
||||||
|
breakpoint={currentBreakpoint}
|
||||||
|
count={count}
|
||||||
|
padding={0}
|
||||||
|
>
|
||||||
|
<ContextMenuResource
|
||||||
|
name={section.content.name}
|
||||||
|
service={section.content.service}
|
||||||
|
identifier={section.content.identifier}
|
||||||
|
link={`qortal://${section?.content?.service}/${section?.content?.name}/${section?.content?.identifier}`}
|
||||||
|
>
|
||||||
|
<FileElement
|
||||||
|
key={section.id}
|
||||||
|
fileInfo={section.content}
|
||||||
|
postId={fullPostId}
|
||||||
|
user={user ? user : ''}
|
||||||
|
title={section.content?.title}
|
||||||
|
description={section.content?.description}
|
||||||
|
mimeType={section.content?.mimeType}
|
||||||
|
author=""
|
||||||
|
/>
|
||||||
|
</ContextMenuResource>
|
||||||
|
</DynamicHeightItemMinimal>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<ReusableModal open={isOpenSwitchPlaylistModal}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography>
|
||||||
|
{tempSaveAudio?.current?.message
|
||||||
|
? tempSaveAudio?.current?.message
|
||||||
|
: 'You are current on a playlist. Would you like to switch?'}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => setisOpenSwitchPlaylistModal(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" onClick={switchPlayList}>
|
||||||
|
Switch
|
||||||
|
</Button>
|
||||||
|
</ReusableModal>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Content = ({
|
||||||
|
children,
|
||||||
|
layouts,
|
||||||
|
blogContent,
|
||||||
|
onResizeStop,
|
||||||
|
onBreakpointChange,
|
||||||
|
handleLayoutChange
|
||||||
|
}: any) => {
|
||||||
|
if (layouts && blogContent?.layouts) {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<Typography>Error loading content: Invalid Layout</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ResponsiveGridLayout
|
||||||
|
layouts={layouts}
|
||||||
|
breakpoints={{ md: 996, sm: 768, xs: 480 }}
|
||||||
|
cols={{ md: 4, sm: 3, xs: 1 }}
|
||||||
|
measureBeforeMount={false}
|
||||||
|
onLayoutChange={handleLayoutChange}
|
||||||
|
autoSize={true}
|
||||||
|
compactType={null}
|
||||||
|
isBounded={true}
|
||||||
|
resizeHandles={['se', 'sw', 'ne', 'nw']}
|
||||||
|
rowHeight={25}
|
||||||
|
onResizeStop={onResizeStop}
|
||||||
|
onBreakpointChange={onBreakpointChange}
|
||||||
|
isDraggable={false}
|
||||||
|
isResizable={false}
|
||||||
|
margin={[0, 0]}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ResponsiveGridLayout>
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
1586
CreatePostBuilder.tsx
Normal file
1586
CreatePostBuilder.tsx
Normal file
File diff suppressed because it is too large
Load Diff
1565
CreatePostMinimal.tsx
Normal file
1565
CreatePostMinimal.tsx
Normal file
File diff suppressed because it is too large
Load Diff
832
VideoPlayer.tsx
Normal file
832
VideoPlayer.tsx
Normal file
@@ -0,0 +1,832 @@
|
|||||||
|
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import { Box, IconButton, Slider } from '@mui/material'
|
||||||
|
import { CircularProgress, Typography } from '@mui/material'
|
||||||
|
import { Key } from 'ts-key-enum'
|
||||||
|
import {
|
||||||
|
PlayArrow,
|
||||||
|
Pause,
|
||||||
|
VolumeUp,
|
||||||
|
Fullscreen,
|
||||||
|
PictureInPicture, VolumeOff
|
||||||
|
} from '@mui/icons-material'
|
||||||
|
import { styled } from '@mui/system'
|
||||||
|
import { MyContext } from '../../wrappers/DownloadWrapper'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { RootState } from '../../state/store'
|
||||||
|
import { Refresh } from '@mui/icons-material'
|
||||||
|
|
||||||
|
import { Menu, MenuItem } from '@mui/material'
|
||||||
|
import { MoreVert as MoreIcon } from '@mui/icons-material'
|
||||||
|
const VideoContainer = styled(Box)`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const VideoElement = styled('video')`
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
background: rgb(33, 33, 33);
|
||||||
|
`
|
||||||
|
|
||||||
|
const ControlsContainer = styled(Box)`
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
`
|
||||||
|
|
||||||
|
interface VideoPlayerProps {
|
||||||
|
src?: string
|
||||||
|
poster?: string
|
||||||
|
name?: string
|
||||||
|
identifier?: string
|
||||||
|
service?: string
|
||||||
|
autoplay?: boolean
|
||||||
|
from?: string | null
|
||||||
|
setCount?: () => void
|
||||||
|
customStyle?: any
|
||||||
|
user?: string
|
||||||
|
postId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
|
poster,
|
||||||
|
name,
|
||||||
|
identifier,
|
||||||
|
service,
|
||||||
|
autoplay = true,
|
||||||
|
from = null,
|
||||||
|
setCount,
|
||||||
|
customStyle = {},
|
||||||
|
user = '',
|
||||||
|
postId = ''
|
||||||
|
}) => {
|
||||||
|
const videoRef = useRef<HTMLVideoElement | null>(null)
|
||||||
|
const [playing, setPlaying] = useState(false)
|
||||||
|
const [volume, setVolume] = useState(1)
|
||||||
|
const [mutedVolume, setMutedVolume] = useState(1)
|
||||||
|
const [isMuted, setIsMuted] = useState(false)
|
||||||
|
const [progress, setProgress] = useState(0)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [canPlay, setCanPlay] = useState(false)
|
||||||
|
const [startPlay, setStartPlay] = useState(false)
|
||||||
|
const [isMobileView, setIsMobileView] = useState(false)
|
||||||
|
const [playbackRate, setPlaybackRate] = useState(1)
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
|
const [consoleLog, setConsoleLog] = useState('Console Log Here')
|
||||||
|
const [debug, setDebug] = useState(false)
|
||||||
|
|
||||||
|
const reDownload = useRef<boolean>(false)
|
||||||
|
const { downloads } = useSelector((state: RootState) => state.global)
|
||||||
|
const download = useMemo(() => {
|
||||||
|
if (!downloads || !identifier) return {}
|
||||||
|
const findDownload = downloads[identifier]
|
||||||
|
|
||||||
|
if (!findDownload) return {}
|
||||||
|
return findDownload
|
||||||
|
}, [downloads, identifier])
|
||||||
|
|
||||||
|
const src = useMemo(() => {
|
||||||
|
return download?.url || ''
|
||||||
|
}, [download?.url])
|
||||||
|
const resourceStatus = useMemo(() => {
|
||||||
|
return download?.status || {}
|
||||||
|
}, [download])
|
||||||
|
|
||||||
|
const minSpeed = 0.25;
|
||||||
|
const maxSpeed = 4.0;
|
||||||
|
const speedChange = 0.25;
|
||||||
|
|
||||||
|
const updatePlaybackRate = (newSpeed: number) => {
|
||||||
|
if(videoRef.current) {
|
||||||
|
if(newSpeed > maxSpeed || newSpeed < minSpeed)
|
||||||
|
newSpeed = minSpeed
|
||||||
|
videoRef.current.playbackRate = newSpeed
|
||||||
|
setPlaybackRate(newSpeed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const increaseSpeed = (wrapOverflow = true) => {
|
||||||
|
const changedSpeed = playbackRate + speedChange
|
||||||
|
let newSpeed = wrapOverflow ? changedSpeed: Math.min(changedSpeed, maxSpeed)
|
||||||
|
|
||||||
|
|
||||||
|
if (videoRef.current) {
|
||||||
|
updatePlaybackRate(newSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decreaseSpeed = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
updatePlaybackRate(playbackRate - speedChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const toggleRef = useRef<any>(null)
|
||||||
|
const { downloadVideo } = useContext(MyContext)
|
||||||
|
const togglePlay = async () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
setStartPlay(true)
|
||||||
|
if (!src) {
|
||||||
|
const el = document.getElementById('videoWrapper')
|
||||||
|
if (el) {
|
||||||
|
el?.parentElement?.removeChild(el)
|
||||||
|
}
|
||||||
|
ReactDOM.flushSync(() => {
|
||||||
|
setIsLoading(true)
|
||||||
|
})
|
||||||
|
getSrc()
|
||||||
|
}
|
||||||
|
if (playing) {
|
||||||
|
videoRef.current.pause()
|
||||||
|
} else {
|
||||||
|
videoRef.current.play()
|
||||||
|
}
|
||||||
|
setPlaying(!playing)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onVolumeChange = (_: any, value: number | number[]) => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
videoRef.current.volume = value as number
|
||||||
|
setVolume(value as number)
|
||||||
|
setIsMuted(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onProgressChange = (_: any, value: number | number[]) => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
videoRef.current.currentTime = value as number
|
||||||
|
setProgress(value as number)
|
||||||
|
if (!playing) {
|
||||||
|
videoRef.current.play()
|
||||||
|
setPlaying(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEnded = () => {
|
||||||
|
setPlaying(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProgress = () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
setProgress(videoRef.current.currentTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(false)
|
||||||
|
|
||||||
|
const enterFullscreen = () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
if (videoRef.current.requestFullscreen) {
|
||||||
|
videoRef.current.requestFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exitFullscreen = () => {
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
isFullscreen ? exitFullscreen(): enterFullscreen()
|
||||||
|
}
|
||||||
|
const togglePictureInPicture = async () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
if (document.pictureInPictureElement === videoRef.current) {
|
||||||
|
await document.exitPictureInPicture()
|
||||||
|
} else {
|
||||||
|
await videoRef.current.requestPictureInPicture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleFullscreenChange = () => {
|
||||||
|
setIsFullscreen(!!document.fullscreenElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleLoadedMetadata = () => {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCanPlay = () => {
|
||||||
|
if (setCount) {
|
||||||
|
setCount()
|
||||||
|
}
|
||||||
|
setIsLoading(false)
|
||||||
|
setCanPlay(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSrc = React.useCallback(async () => {
|
||||||
|
if (!name || !identifier || !service || !postId || !user) return
|
||||||
|
try {
|
||||||
|
downloadVideo({
|
||||||
|
name,
|
||||||
|
service,
|
||||||
|
identifier,
|
||||||
|
blogPost: {
|
||||||
|
postId,
|
||||||
|
user
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {}
|
||||||
|
}, [identifier, name, service])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const videoElement = videoRef.current
|
||||||
|
|
||||||
|
const handleLeavePictureInPicture = async (event: any) => {
|
||||||
|
const target = event?.target
|
||||||
|
if (target) {
|
||||||
|
target.pause()
|
||||||
|
if (setPlaying) {
|
||||||
|
setPlaying(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoElement) {
|
||||||
|
videoElement.addEventListener(
|
||||||
|
'leavepictureinpicture',
|
||||||
|
handleLeavePictureInPicture
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (videoElement) {
|
||||||
|
videoElement.removeEventListener(
|
||||||
|
'leavepictureinpicture',
|
||||||
|
handleLeavePictureInPicture
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const videoElement = videoRef.current
|
||||||
|
|
||||||
|
const minimizeVideo = async () => {
|
||||||
|
if (!videoElement) return
|
||||||
|
const handleClose = () => {
|
||||||
|
if (videoElement && videoElement.parentElement) {
|
||||||
|
const el = document.getElementById('videoWrapper')
|
||||||
|
if (el) {
|
||||||
|
el?.parentElement?.removeChild(el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const createCloseButton = (): HTMLButtonElement => {
|
||||||
|
const closeButton = document.createElement('button')
|
||||||
|
closeButton.textContent = 'X'
|
||||||
|
closeButton.style.position = 'absolute'
|
||||||
|
closeButton.style.top = '0'
|
||||||
|
closeButton.style.right = '0'
|
||||||
|
closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.7)'
|
||||||
|
closeButton.style.border = 'none'
|
||||||
|
closeButton.style.fontWeight = 'bold'
|
||||||
|
closeButton.style.fontSize = '1.2rem'
|
||||||
|
closeButton.style.cursor = 'pointer'
|
||||||
|
closeButton.style.padding = '2px 8px'
|
||||||
|
closeButton.style.borderRadius = '0 0 0 4px'
|
||||||
|
|
||||||
|
closeButton.addEventListener('click', handleClose)
|
||||||
|
|
||||||
|
return closeButton
|
||||||
|
}
|
||||||
|
const buttonClose = createCloseButton()
|
||||||
|
const videoWrapper = document.createElement('div')
|
||||||
|
videoWrapper.id = 'videoWrapper'
|
||||||
|
videoWrapper.style.position = 'fixed'
|
||||||
|
videoWrapper.style.zIndex = '900000009'
|
||||||
|
videoWrapper.style.bottom = '0px'
|
||||||
|
videoWrapper.style.right = '0px'
|
||||||
|
|
||||||
|
videoElement.parentElement?.insertBefore(videoWrapper, videoElement)
|
||||||
|
videoWrapper.appendChild(videoElement)
|
||||||
|
|
||||||
|
videoWrapper.appendChild(buttonClose)
|
||||||
|
videoElement.controls = true
|
||||||
|
videoElement.style.height = 'auto'
|
||||||
|
videoElement.style.width = '300px'
|
||||||
|
|
||||||
|
document.body.appendChild(videoWrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (videoElement) {
|
||||||
|
if (videoElement && !videoElement.paused && !videoElement.ended) {
|
||||||
|
minimizeVideo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
function formatTime(seconds: number): string {
|
||||||
|
seconds = Math.floor(seconds)
|
||||||
|
let minutes: number | string = Math.floor(seconds / 60)
|
||||||
|
let hours: number | string = Math.floor(minutes / 60)
|
||||||
|
|
||||||
|
let remainingSeconds: number | string = seconds % 60
|
||||||
|
let remainingMinutes: number | string = minutes % 60
|
||||||
|
|
||||||
|
if (remainingSeconds < 10) {
|
||||||
|
remainingSeconds = '0' + remainingSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingMinutes < 10) {
|
||||||
|
remainingMinutes = '0' + remainingMinutes
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hours === 0){
|
||||||
|
hours = ''
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hours = hours + ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
return hours + remainingMinutes + ':' + remainingSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadVideo = () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
const currentTime = videoRef.current.currentTime
|
||||||
|
videoRef.current.src = src
|
||||||
|
videoRef.current.load()
|
||||||
|
videoRef.current.currentTime = currentTime
|
||||||
|
if (playing) {
|
||||||
|
videoRef.current.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
resourceStatus?.status === 'DOWNLOADED' &&
|
||||||
|
reDownload?.current === false
|
||||||
|
) {
|
||||||
|
getSrc()
|
||||||
|
reDownload.current = true
|
||||||
|
}
|
||||||
|
}, [getSrc, resourceStatus])
|
||||||
|
|
||||||
|
const handleMenuOpen = (event: any) => {
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMenuClose = () => {
|
||||||
|
setAnchorEl(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const videoWidth = videoRef?.current?.offsetWidth
|
||||||
|
if (videoWidth && videoWidth <= 600) {
|
||||||
|
setIsMobileView(true)
|
||||||
|
}
|
||||||
|
}, [canPlay])
|
||||||
|
|
||||||
|
const getDownloadProgress = (current: number, total: number) => {
|
||||||
|
const progress = current /total * 100;
|
||||||
|
return Number.isNaN(progress) ? '': progress.toFixed(0)+'%'
|
||||||
|
}
|
||||||
|
const mute = () => {
|
||||||
|
setIsMuted(true)
|
||||||
|
setMutedVolume(volume)
|
||||||
|
setVolume(0)
|
||||||
|
if(videoRef.current) videoRef.current.volume = 0
|
||||||
|
}
|
||||||
|
const unMute = () => {
|
||||||
|
setIsMuted(false)
|
||||||
|
setVolume(mutedVolume)
|
||||||
|
if(videoRef.current) videoRef.current.volume = mutedVolume
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleMute = () => {
|
||||||
|
isMuted ? unMute() : mute();
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeVolume = (volumeChange: number) =>
|
||||||
|
{
|
||||||
|
if(videoRef.current){
|
||||||
|
const minVolume = 0;
|
||||||
|
const maxVolume = 1;
|
||||||
|
|
||||||
|
|
||||||
|
let newVolume = volumeChange + volume
|
||||||
|
|
||||||
|
newVolume = Math.max(newVolume, minVolume)
|
||||||
|
newVolume = Math.min(newVolume, maxVolume)
|
||||||
|
|
||||||
|
setIsMuted(false)
|
||||||
|
setMutedVolume(newVolume)
|
||||||
|
videoRef.current.volume = newVolume
|
||||||
|
setVolume(newVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
const setProgressRelative = (secondsChange: number) => {
|
||||||
|
if(videoRef.current){
|
||||||
|
const currentTime = videoRef.current?.currentTime
|
||||||
|
const minTime = 0
|
||||||
|
const maxTime = videoRef.current?.duration || 100
|
||||||
|
|
||||||
|
let newTime = currentTime + secondsChange;
|
||||||
|
newTime = Math.max(newTime, minTime)
|
||||||
|
newTime = Math.min(newTime, maxTime)
|
||||||
|
videoRef.current.currentTime = newTime;
|
||||||
|
setProgress(newTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setProgressAbsolute = (videoPercent: number) => {
|
||||||
|
if(videoRef.current){
|
||||||
|
videoPercent = Math.min(videoPercent, 100)
|
||||||
|
videoPercent = Math.max(videoPercent, 0)
|
||||||
|
const finalTime = videoRef.current?.duration*videoPercent / 100
|
||||||
|
videoRef.current.currentTime = finalTime
|
||||||
|
setProgress(finalTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) =>
|
||||||
|
{
|
||||||
|
e.preventDefault()
|
||||||
|
//setConsoleLog(`Alt: ${e.altKey} Shift: ${e.shiftKey} Control: ${e.ctrlKey} Key: ${e.key}`)
|
||||||
|
|
||||||
|
switch(e.key) {
|
||||||
|
case Key.Add: increaseSpeed(false); break;
|
||||||
|
case '+': increaseSpeed(false); break;
|
||||||
|
case '>': increaseSpeed(false); break;
|
||||||
|
|
||||||
|
case Key.Subtract: decreaseSpeed(); break;
|
||||||
|
case '-': decreaseSpeed(); break;
|
||||||
|
case '<': decreaseSpeed(); break;
|
||||||
|
|
||||||
|
case Key.ArrowLeft: {
|
||||||
|
if(e.shiftKey) setProgressRelative(-300);
|
||||||
|
else if(e.ctrlKey) setProgressRelative(-60);
|
||||||
|
else if(e.altKey) setProgressRelative(-10);
|
||||||
|
else setProgressRelative(-5);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Key.ArrowRight: {
|
||||||
|
if(e.shiftKey) setProgressRelative(300);
|
||||||
|
else if(e.ctrlKey) setProgressRelative(60);
|
||||||
|
else if(e.altKey) setProgressRelative(10);
|
||||||
|
else setProgressRelative(5);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Key.ArrowDown: changeVolume(-0.05) ; break;
|
||||||
|
case Key.ArrowUp: changeVolume(0.05) ; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyboardShortcutsUp = (e: React.KeyboardEvent<HTMLDivElement>) =>
|
||||||
|
{
|
||||||
|
e.preventDefault()
|
||||||
|
//setConsoleLog(`Alt: ${e.altKey} Shift: ${e.shiftKey} Control: ${e.ctrlKey} Key: ${e.key}`)
|
||||||
|
|
||||||
|
switch(e.key) {
|
||||||
|
case ' ': togglePlay(); break;
|
||||||
|
case 'm': toggleMute(); break;
|
||||||
|
|
||||||
|
case 'f': enterFullscreen(); break;
|
||||||
|
case Key.Escape: exitFullscreen(); break;
|
||||||
|
|
||||||
|
case '0': setProgressAbsolute(0); break;
|
||||||
|
case '1': setProgressAbsolute(10); break;
|
||||||
|
case '2': setProgressAbsolute(20); break;
|
||||||
|
case '3': setProgressAbsolute(30); break;
|
||||||
|
case '4': setProgressAbsolute(40); break;
|
||||||
|
case '5': setProgressAbsolute(50); break;
|
||||||
|
case '6': setProgressAbsolute(60); break;
|
||||||
|
case '7': setProgressAbsolute(70); break;
|
||||||
|
case '8': setProgressAbsolute(80); break;
|
||||||
|
case '9': setProgressAbsolute(90); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VideoContainer
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyUp={keyboardShortcutsUp}
|
||||||
|
onKeyDown={keyboardShortcutsDown}
|
||||||
|
style={{
|
||||||
|
padding: from === 'create' ? '8px' : 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-30px',
|
||||||
|
right: '-15px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyToClipboard
|
||||||
|
text={`qortal://${service}/${name}/${identifier}`}
|
||||||
|
onCopy={() => {
|
||||||
|
dispatch(
|
||||||
|
setNotification({
|
||||||
|
msg: 'Copied to clipboard!',
|
||||||
|
alertType: 'success'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LinkIcon
|
||||||
|
sx={{
|
||||||
|
fontSize: '14px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CopyToClipboard>
|
||||||
|
</Box> */}
|
||||||
|
{isLoading && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={resourceStatus?.status === 'READY' ? '55px ' : 0}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
zIndex={4999}
|
||||||
|
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress color="secondary" />
|
||||||
|
{resourceStatus && (
|
||||||
|
<Typography
|
||||||
|
variant="subtitle2"
|
||||||
|
component="div"
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '15px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{resourceStatus?.status === 'REFETCHING' ? (
|
||||||
|
<>
|
||||||
|
<>
|
||||||
|
{getDownloadProgress(resourceStatus?.localChunkCount,resourceStatus?.totalChunkCount)}
|
||||||
|
</>
|
||||||
|
|
||||||
|
<> Refetching in 25 seconds</>
|
||||||
|
</>
|
||||||
|
) : resourceStatus?.status === 'DOWNLOADED' ? (
|
||||||
|
<>Download Completed: building video...</>
|
||||||
|
) : resourceStatus?.status !== 'READY' ? (
|
||||||
|
<>
|
||||||
|
{getDownloadProgress(resourceStatus?.localChunkCount,resourceStatus?.totalChunkCount)}
|
||||||
|
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>Download Completed: fetching video...</>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{((!src && !isLoading) || !startPlay) && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
zIndex={500}
|
||||||
|
bgcolor={poster ? 'transparent' : 'rgba(0, 0, 0, 0.6)'}
|
||||||
|
onClick={() => {
|
||||||
|
if (from === 'create') return
|
||||||
|
|
||||||
|
togglePlay()
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayArrow
|
||||||
|
sx={{
|
||||||
|
width: '50px',
|
||||||
|
height: '50px',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<VideoElement
|
||||||
|
ref={videoRef}
|
||||||
|
src={!startPlay ? '' : resourceStatus?.status === 'READY' ? src : ''}
|
||||||
|
poster={poster}
|
||||||
|
onTimeUpdate={updateProgress}
|
||||||
|
autoPlay={autoplay}
|
||||||
|
onClick={togglePlay}
|
||||||
|
onEnded={handleEnded}
|
||||||
|
// onLoadedMetadata={handleLoadedMetadata}
|
||||||
|
onCanPlay={handleCanPlay}
|
||||||
|
preload="metadata"
|
||||||
|
style={{
|
||||||
|
...customStyle
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ControlsContainer
|
||||||
|
style={{
|
||||||
|
bottom: from === 'create' ? '15px' : 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isMobileView && canPlay ? (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)'
|
||||||
|
}}
|
||||||
|
onClick={togglePlay}
|
||||||
|
>
|
||||||
|
{playing ? <Pause /> : <PlayArrow />}
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
marginLeft: '15px'
|
||||||
|
}}
|
||||||
|
onClick={reloadVideo}
|
||||||
|
>
|
||||||
|
<Refresh />
|
||||||
|
</IconButton>
|
||||||
|
<Slider
|
||||||
|
value={progress}
|
||||||
|
onChange={onProgressChange}
|
||||||
|
min={0}
|
||||||
|
max={videoRef.current?.duration || 100}
|
||||||
|
sx={{ flexGrow: 1, mx: 2 }}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
color="inherit"
|
||||||
|
aria-label="menu"
|
||||||
|
onClick={handleMenuOpen}
|
||||||
|
>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
id="simple-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
keepMounted
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleMenuClose}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
width: '250px'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem>
|
||||||
|
<VolumeUp />
|
||||||
|
<Slider
|
||||||
|
value={volume}
|
||||||
|
onChange={onVolumeChange}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}/>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => increaseSpeed()}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Speed: {playbackRate}x
|
||||||
|
</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={togglePictureInPicture}>
|
||||||
|
<PictureInPicture />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={toggleFullscreen}>
|
||||||
|
<Fullscreen />
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
) : canPlay ? (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)'
|
||||||
|
}}
|
||||||
|
onClick={togglePlay}
|
||||||
|
>
|
||||||
|
{playing ? <Pause /> : <PlayArrow />}
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
marginLeft: '15px'
|
||||||
|
}}
|
||||||
|
onClick={reloadVideo}
|
||||||
|
>
|
||||||
|
<Refresh />
|
||||||
|
</IconButton>
|
||||||
|
<Slider
|
||||||
|
value={progress}
|
||||||
|
onChange={onProgressChange}
|
||||||
|
min={0}
|
||||||
|
max={videoRef.current?.duration || 100}
|
||||||
|
sx={{ flexGrow: 1, mx: 2 }}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '5px',
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
visibility:
|
||||||
|
!videoRef.current?.duration || !progress
|
||||||
|
? 'hidden'
|
||||||
|
: 'visible'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{progress && videoRef.current?.duration && formatTime(progress)}/
|
||||||
|
{progress &&
|
||||||
|
videoRef.current?.duration &&
|
||||||
|
formatTime(videoRef.current?.duration)}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
marginRight: '10px'
|
||||||
|
}}
|
||||||
|
onClick={toggleMute}
|
||||||
|
>
|
||||||
|
{isMuted ? <VolumeOff/>:<VolumeUp/>}
|
||||||
|
</IconButton>
|
||||||
|
<Slider
|
||||||
|
value={volume}
|
||||||
|
onChange={onVolumeChange}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginLeft: '5px'
|
||||||
|
}}
|
||||||
|
onClick={(e) => increaseSpeed()}
|
||||||
|
>
|
||||||
|
Speed: {playbackRate}x
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
marginLeft: '15px'
|
||||||
|
}}
|
||||||
|
ref={toggleRef}
|
||||||
|
onClick={togglePictureInPicture}
|
||||||
|
>
|
||||||
|
<PictureInPicture />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)'
|
||||||
|
}}
|
||||||
|
onClick={toggleFullscreen}
|
||||||
|
>
|
||||||
|
<Fullscreen />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</ControlsContainer>
|
||||||
|
{debug ? <span>{consoleLog}</span>: <></>}
|
||||||
|
</VideoContainer>
|
||||||
|
)
|
||||||
|
}
|
Reference in New Issue
Block a user