Skip to content

Commit

Permalink
修复录制视频无法播放bug。
Browse files Browse the repository at this point in the history
  • Loading branch information
tengge1 committed Sep 8, 2019
1 parent 3398ae5 commit c4d527a
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 11 deletions.
2 changes: 1 addition & 1 deletion ShadowEditor.Server/Controllers/VideoController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public JsonResult Add()
["SaveName"] = fileName,
["SavePath"] = savePath,
["Url"] = $"{savePath}/{fileName}",
["Thumbnail"] = $"{savePath}/{fileName}",
["Thumbnail"] = "", // TODO: 从视频获取截图
["CreateTime"] = now,
["UpdateTime"] = now,
};
Expand Down
3 changes: 2 additions & 1 deletion ShadowEditor.Web/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -695,5 +695,6 @@
"premultipliedAlpha": "左乘透明",
"preserveDrawingBuffer": "保留绘制缓冲区",
"stencil": "模板",
"Screenshot": "截图"
"Screenshot": "截图",
"Video": "视频"
}
24 changes: 23 additions & 1 deletion ShadowEditor.Web/src/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
Alert,
Confirm,
Prompt,
Photo
Photo,
Video
} from './third_party';
import Options from './Options';
import Storage from './utils/Storage';
Expand Down Expand Up @@ -301,6 +302,27 @@ Application.prototype.photo = function (url) {
this.addElement(component);
};

/**
* 查看视频
* @param {String} url 地址
*/
Application.prototype.video = function (url) {
let component = null;

let close = () => {
if (component) {
this.removeElement(component);
component = null;
}
};

component = this.createElement(Video, {
url,
onClick: close
});

this.addElement(component);
};

// -------------------- 工具函数 -----------------------

Expand Down
4 changes: 4 additions & 0 deletions ShadowEditor.Web/src/editor/assets/AssetsPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ParticlePanel from './ParticlePanel.jsx';
import PrefabPanel from './PrefabPanel.jsx';
import CharacterPanel from './CharacterPanel.jsx';
import ScreenshotPanel from './ScreenshotPanel.jsx';
import VideoPanel from './VideoPanel.jsx';
import LogPanel from './LogPanel.jsx';

/**
Expand Down Expand Up @@ -74,6 +75,9 @@ class AssetsPanel extends React.Component {
<Accordion name={'Screenshot'} title={`${_t('Screenshot')}(${screenshotCount})`} maximizable={true}>
<ScreenshotPanel className={'subPanel'} show={9 === activeIndex}></ScreenshotPanel>
</Accordion>
<Accordion name={'Video'} title={`${_t('Video')}(${videoCount})`} maximizable={true}>
<VideoPanel className={'subPanel'} show={10 === activeIndex}></VideoPanel>
</Accordion>
<Accordion name={'Log'} title={`${_t('Logs')}`} maximizable={true}>
<LogPanel className={'subPanel'} show={11 === activeIndex}></LogPanel>
</Accordion>
Expand Down
156 changes: 156 additions & 0 deletions ShadowEditor.Web/src/editor/assets/VideoPanel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { classNames, PropTypes, SearchField, ImageList } from '../../third_party';
import EditWindow from './window/EditWindow.jsx';
import ModelLoader from '../../loader/ModelLoader';
import AddObjectCommand from '../../command/AddObjectCommand';
import UploadUtils from '../../utils/UploadUtils';

/**
* 视频面板
* @author tengge / https://github.com/tengge1
*/
class VideoPanel extends React.Component {
constructor(props) {
super(props);

this.state = {
data: [],
categoryData: [],
name: '',
categories: [],
};

this.handleClick = this.handleClick.bind(this);

this.handleEdit = this.handleEdit.bind(this);
this.handleDelete = this.handleDelete.bind(this);

this.update = this.update.bind(this);
}

render() {
const { className, style } = this.props;
const { data, categoryData, name, categories } = this.state;

let list = data;

if (name.trim() !== '') {
list = list.filter(n => {
return n.Name.toLowerCase().indexOf(name.toLowerCase()) > -1 ||
n.FirstPinYin.toLowerCase().indexOf(name.toLowerCase()) > -1 ||
n.TotalPinYin.toLowerCase().indexOf(name.toLowerCase()) > -1;
});
}

if (categories.length > 0) {
list = list.filter(n => {
return categories.indexOf(n.CategoryID) > -1;
});
}

const imageListData = list.map(n => {
return Object.assign({}, n, {
id: n.ID,
src: n.Thumbnail ? `${app.options.server}${n.Thumbnail}` : null,
title: n.Name,
icon: 'scenes',
});
});

return <div className={classNames('VideoPanel', className)} style={style}>
<SearchField
data={categoryData}
placeholder={_t('Search Content')}
addHidden={true}
onInput={this.handleSearch.bind(this)}></SearchField>
<ImageList
data={imageListData}
onClick={this.handleClick}
onEdit={this.handleEdit}
onDelete={this.handleDelete}></ImageList>
</div>;
}

componentDidUpdate(prevProps, prevState) {
if (this.init === undefined && this.props.show === true) {
this.init = true;
this.update();
}
}

update() {
fetch(`${app.options.server}/api/Category/List?type=Video`).then(response => {
response.json().then(obj => {
this.setState({
categoryData: obj.Data,
});
});
});
fetch(`${app.options.server}/api/Video/List`).then(response => {
response.json().then(obj => {
this.setState({
data: obj.Data,
});
});
});
}

handleSearch(name, categories, event) {
this.setState({
name,
categories,
});
}

handleClick(data) {
app.video(data.Url);
}

// ------------------------------- 编辑 ---------------------------------------

handleEdit(data) {
var win = app.createElement(EditWindow, {
type: 'Video',
typeName: _t('Video'),
data,
saveUrl: `${app.options.server}/api/Video/Edit`,
callback: this.update,
});

app.addElement(win);
}

// ------------------------------ 删除 ----------------------------------------

handleDelete(data) {
app.confirm({
title: _t('Confirm'),
content: `${_t('Delete')} ${data.title}?`,
onOK: () => {
fetch(`${app.options.server}/api/Video/Delete?ID=${data.id}`, {
method: 'POST',
}).then(response => {
response.json().then(obj => {
if (obj.Code === 200) {
this.update();
}
app.toast(obj.Msg);
});
});
}
});
}
}

VideoPanel.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
show: PropTypes.bool,
};

VideoPanel.defaultProps = {
className: null,
style: null,
show: false,
};

export default VideoPanel;
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class CategoryWindow extends React.Component {
}

CategoryWindow.propTypes = {
type: PropTypes.oneOf(['Scene', 'Mesh', 'Map', 'Texture', 'Material', 'Audio', 'Particle', 'Screenshot']),
type: PropTypes.oneOf(['Scene', 'Mesh', 'Map', 'Texture', 'Material', 'Audio', 'Particle', 'Screenshot', 'Video']),
typeName: PropTypes.string,
callback: PropTypes.func,
};
Expand Down
2 changes: 1 addition & 1 deletion ShadowEditor.Web/src/editor/assets/window/EditWindow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class EditWindow extends React.Component {
}

EditWindow.propTypes = {
type: PropTypes.oneOf(['Scene', 'Mesh', 'Map', 'Texture', 'Material', 'Audio', 'Particle', 'Screenshot']),
type: PropTypes.oneOf(['Scene', 'Mesh', 'Map', 'Texture', 'Material', 'Audio', 'Particle', 'Screenshot', 'Video']),
typeName: PropTypes.string,
data: PropTypes.object,
saveUrl: PropTypes.string,
Expand Down
2 changes: 1 addition & 1 deletion ShadowEditor.Web/src/editor/status/EditorStatusBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class EditorStatusBar extends React.Component {
<CheckBox checked={isThrowBall} onChange={this.handleEnableThrowBall}></CheckBox>
<ToolbarSeparator></ToolbarSeparator>
<Button onClick={this.handleScreenshot}>{_t('Screenshot')}</Button>
<Button onClick={this.handleRecord}>{isRecording ? _t('Cancel') : _t('Record')}</Button>
<Button onClick={this.handleRecord}>{isRecording ? _t('Stop') : _t('Record')}</Button>
</Toolbar>;
}

Expand Down
1 change: 1 addition & 0 deletions ShadowEditor.Web/src/ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,5 @@ export { default as Confirm } from './window/Confirm.jsx';
export { default as Photo } from './window/Photo.jsx';
export { default as Prompt } from './window/Prompt.jsx';
export { default as Toast } from './window/Toast.jsx';
export { default as Video } from './window/Video.jsx';
export { default as Window } from './window/Window.jsx';
47 changes: 47 additions & 0 deletions ShadowEditor.Web/src/ui/window/Video.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import './css/Video.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';

/**
* 视频查看器
*/
class Video extends React.Component {
constructor(props) {
super(props);

this.handleClick = this.handleClick.bind(this, props.onClick);
this.handleClickImage = this.handleClickVideo.bind(this);
}

render() {
const { className, style, url } = this.props;

return <div className={'VideoMark'} onClick={this.handleClick}>
<video src={url} autoPlay={'autoplay'} controls={'controls'} onClick={this.handleClickVideo} />
</div>;
}

handleClick(onClick, event) {
onClick && onClick(event);
}

handleClickVideo(event) {
event.stopPropagation();
}
}

Video.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
url: PropTypes.string,
onClick: PropTypes.func,
};

Video.defaultProps = {
className: null,
style: null,
url: null,
onClick: null,
};

export default Video;
16 changes: 16 additions & 0 deletions ShadowEditor.Web/src/ui/window/css/Video.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.VideoMark {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1300;
}

.VideoMark>video {
max-width: 80%;
}
22 changes: 17 additions & 5 deletions ShadowEditor.Web/src/utils/VideoRecorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,29 @@ class VideoRecorder {

stop() {
return new Promise(resolve => {
this.recorder.onstop = () => {
this.recorder.onstop = (e) => {
this.recorder.ondataavailable = null;
this.recorder.onstop = null;

let fileName = TimeUtils.getDateTime() + '.webm';
const file = new File(this.chunks, TimeUtils.getDateTime() + '.webm');

DownloadUtils.download(this.chunks, { 'type': 'video/x-matroska;codecs=avc1' }, fileName);
let form = new FormData();
form.append('file', file);

this.chunks.length = 0;
fetch(`/api/Video/Add`, {
method: 'POST',
body: form
}).then(response => {
response.json().then(json => {
app.toast(json.Msg);

resolve(true);
if (json.Code === 200) {
this.chunks.length = 0;
}

resolve(true);
});
});
};

this.recorder.stop();
Expand Down

0 comments on commit c4d527a

Please sign in to comment.