diff --git a/frontend/src/components/dirent-detail/dirent-details/index.css b/frontend/src/components/dirent-detail/dirent-details/index.css index 090c4e8f17..4f45647def 100644 --- a/frontend/src/components/dirent-detail/dirent-details/index.css +++ b/frontend/src/components/dirent-detail/dirent-details/index.css @@ -1,6 +1,7 @@ .detail-body .detail-image { height: 144px; width: 100%; + position: relative; flex-shrink: 0; display: flex; align-items: center; @@ -54,3 +55,8 @@ position: absolute; transform: translateY(-50px); } + +.detail-body .video-js { + width: 100%; + height: 100%; +} diff --git a/frontend/src/components/dirent-detail/dirent-details/index.js b/frontend/src/components/dirent-detail/dirent-details/index.js index dd72b4beae..075e4da78d 100644 --- a/frontend/src/components/dirent-detail/dirent-details/index.js +++ b/frontend/src/components/dirent-detail/dirent-details/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { siteRoot, thumbnailSizeForGrid, enableSeafileAI } from '../../../utils/constants'; +import { siteRoot, thumbnailSizeForGrid, enableSeafileAI, fileServerRoot, MimetypesKind } from '../../../utils/constants'; import { seafileAPI } from '../../../utils/seafile-api'; import { Utils } from '../../../utils/utils'; import toaster from '../../toast'; @@ -13,6 +13,7 @@ import AIIcon from '../../../metadata/components/metadata-details/ai-icon'; import SettingsIcon from '../../../metadata/components/metadata-details/settings-icon'; import { eventBus } from '../../common/event-bus'; import { EVENT_BUS_TYPE } from '../../../metadata/constants'; +import VideoPlayer from '../../video-player'; import './index.css'; @@ -22,7 +23,9 @@ class DirentDetails extends React.Component { super(props); this.state = { direntDetail: '', + isHovering: false, }; + this.videoPlayerRef = React.createRef(); } updateDetail = (repoID, dirent, direntPath) => { @@ -57,21 +60,78 @@ class DirentDetails extends React.Component { eventBus.dispatch(EVENT_BUS_TYPE.CLEAR_MAP_INSTANCE); } - renderImage = () => { + handleVideoHover = (isHovering) => { + this.setState({ isHovering }, () => { + if (this.videoPlayerRef.current) { + const player = this.videoPlayerRef.current.player; + if (isHovering) { + player.play(); + } else { + player.pause(); + } + } + }); + }; + + getImageSrc = () => { + const { repoID, path, dirent, currentRepoInfo } = this.props; + return currentRepoInfo.encrypted + ? `${siteRoot}repo/${repoID}/raw${Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`)}` + : `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForGrid}${Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`)}?mtime=${this.state.direntDetail.mtime}`; + }; + + getVideoSrc = () => { + const { repoID, path, dirent } = this.props; + const encodedPath = Utils.encodePath(Utils.joinPath(path, dirent.name)); + return `${fileServerRoot}repos/${repoID}/files${encodedPath}?op=download`; + }; + + renderMedia = () => { const { dirent } = this.props; if (!dirent) return null; - const isImg = Utils.imageCheck(dirent.name); - if (!isImg) return null; - const { repoID, path, currentRepoInfo } = this.props; - let src = ''; - if (currentRepoInfo.encrypted) { - src = `${siteRoot}repo/${repoID}/raw` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`); - } else { - src = `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForGrid}` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`) + '?mtime=' + this.state.direntDetail.mtime; - } + + const isImage = Utils.imageCheck(dirent.name); + const isVideo = Utils.videoCheck(dirent.name); + if (!isImage && !isVideo) return null; + + const src = this.getImageSrc(); + const videoSrc = this.getVideoSrc(); + const mimetype = MimetypesKind[dirent.name.split('.').pop().toLowerCase()] || 'video/mp4'; + + let options = { + autoplay: false, + preload: 'auto', + muted: true, + sources: [{ + src: videoSrc, + type: mimetype + }], + controls: true, + bigPlayButton: false, + controlBar: { + playToggle: false, + volumnPanel: false, + fullscreenToggle: false, + pictureInPictureToggle: false, + children: ['progressControl', 'remainingTimeDisplay'] + } + }; + return ( -
- +
this.handleVideoHover(true)} + onMouseLeave={() => this.handleVideoHover(false)} + > + {isVideo ? ( + + ) : ( + + )}
); }; @@ -84,7 +144,7 @@ class DirentDetails extends React.Component {
- {this.renderImage()} + {this.renderMedia()} ); @@ -106,7 +166,7 @@ class DirentDetails extends React.Component {
- {this.renderImage()} + {this.renderMedia()} {dirent && direntDetail && (
{dirent.type !== 'file' ? ( diff --git a/frontend/src/components/video-player.js b/frontend/src/components/video-player.js index 2d7696009d..0f4acfd41d 100644 --- a/frontend/src/components/video-player.js +++ b/frontend/src/components/video-player.js @@ -23,6 +23,13 @@ class VideoPlayer extends React.Component { this.player.el().focus(); } + componentDidUpdate(prevProps) { + // Update sources if changed + if (JSON.stringify(this.props.sources) !== JSON.stringify(prevProps.sources)) { + this.player.src(this.props.sources[0]); + } + } + // destroy player on unmount componentWillUnmount() { if (this.player) { diff --git a/frontend/src/models/dirent.js b/frontend/src/models/dirent.js index 99734e5d1c..7e4766d346 100644 --- a/frontend/src/models/dirent.js +++ b/frontend/src/models/dirent.js @@ -60,7 +60,7 @@ class Dirent { } toJson() { - return { + const json = { id: this.id, name: this.name, mtime: this.mtime, @@ -71,6 +71,10 @@ class Dirent { modifier_email: this.modifier_email, modifier_contact_email: this.modifier_contact_email, }; + if (this.encoded_thumbnail_src) { + json.encoded_thumbnail_src = this.encoded_thumbnail_src; + } + return json; } }