1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-24 04:48:03 +00:00

Feature/video player in details (#8115)

* details support video player

* optimize

* pause when moving out of player

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
Aries
2025-08-08 16:06:33 +08:00
committed by GitHub
parent e0172fbbb6
commit 110372dcc2
4 changed files with 93 additions and 16 deletions

View File

@@ -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%;
}

View File

@@ -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 (
<div className="detail-image">
<div
className="detail-image"
onMouseEnter={() => this.handleVideoHover(true)}
onMouseLeave={() => this.handleVideoHover(false)}
>
{isVideo ? (
<VideoPlayer
id={`video-player-${dirent.id}`}
ref={this.videoPlayerRef}
{...options}
/>
) : (
<img src={src} alt="" />
)}
</div>
);
};
@@ -84,7 +144,7 @@ class DirentDetails extends React.Component {
<Detail>
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={this.props.onClose} />
<Body>
{this.renderImage()}
{this.renderMedia()}
</Body>
</Detail>
);
@@ -106,7 +166,7 @@ class DirentDetails extends React.Component {
<SettingsIcon />
</Header>
<Body>
{this.renderImage()}
{this.renderMedia()}
{dirent && direntDetail && (
<div className="detail-content">
{dirent.type !== 'file' ? (

View File

@@ -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) {

View File

@@ -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;
}
}