Add videos to be played across all pages. #1523
23 changed files with 391 additions and 25 deletions
12
src/renderer/component/overlay/index.js
Normal file
12
src/renderer/component/overlay/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectShowOverlay } from 'redux/selectors/media';
|
||||||
|
import Overlay from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
showOverlay: selectShowOverlay(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
null
|
||||||
|
)(Overlay);
|
15
src/renderer/component/overlay/view.jsx
Normal file
15
src/renderer/component/overlay/view.jsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ?React.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Overlay extends React.PureComponent<Props> {
|
||||||
|
render() {
|
||||||
|
const { children } = this.props;
|
||||||
|
return <div className="overlay">{children}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Overlay;
|
|
@ -2,8 +2,8 @@ import { connect } from 'react-redux';
|
||||||
import * as settings from 'constants/settings';
|
import * as settings from 'constants/settings';
|
||||||
import { doChangeVolume } from 'redux/actions/app';
|
import { doChangeVolume } from 'redux/actions/app';
|
||||||
import { selectVolume } from 'redux/selectors/app';
|
import { selectVolume } from 'redux/selectors/app';
|
||||||
import { doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
import { doPlayUri, doSetPlayingUri, doLoadVideo } from 'redux/actions/content';
|
||||||
import { doPlay, doPause, savePosition } from 'redux/actions/media';
|
import { doPlay, doPause, savePosition, doHideOverlay, doShowOverlay } from 'redux/actions/media';
|
||||||
import {
|
import {
|
||||||
makeSelectMetadataForUri,
|
makeSelectMetadataForUri,
|
||||||
makeSelectContentTypeForUri,
|
makeSelectContentTypeForUri,
|
||||||
|
@ -15,7 +15,11 @@ import {
|
||||||
selectSearchBarFocused,
|
selectSearchBarFocused,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { makeSelectClientSetting, selectShowNsfw } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectShowNsfw } from 'redux/selectors/settings';
|
||||||
import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media';
|
import {
|
||||||
|
selectMediaPaused,
|
||||||
|
makeSelectMediaPositionForUri,
|
||||||
|
selectShowOverlay,
|
||||||
|
} from 'redux/selectors/media';
|
||||||
import { selectPlayingUri } from 'redux/selectors/content';
|
import { selectPlayingUri } from 'redux/selectors/content';
|
||||||
import Video from './view';
|
import Video from './view';
|
||||||
|
|
||||||
|
@ -34,14 +38,20 @@ const select = (state, props) => ({
|
||||||
mediaPosition: makeSelectMediaPositionForUri(props.uri)(state),
|
mediaPosition: makeSelectMediaPositionForUri(props.uri)(state),
|
||||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||||
searchBarFocused: selectSearchBarFocused(state),
|
searchBarFocused: selectSearchBarFocused(state),
|
||||||
|
showOverlay: selectShowOverlay(state),
|
||||||
|
hiddenControls: props.hiddenControls,
|
||||||
|
fromOverlay: props.fromOverlay,
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
play: uri => dispatch(doPlayUri(uri)),
|
play: uri => dispatch(doPlayUri(uri)),
|
||||||
|
load: uri => dispatch(doLoadVideo(uri)),
|
||||||
cancelPlay: () => dispatch(doSetPlayingUri(null)),
|
cancelPlay: () => dispatch(doSetPlayingUri(null)),
|
||||||
changeVolume: volume => dispatch(doChangeVolume(volume)),
|
changeVolume: volume => dispatch(doChangeVolume(volume)),
|
||||||
doPlay: () => dispatch(doPlay()),
|
doPlay: () => dispatch(doPlay()),
|
||||||
doPause: () => dispatch(doPause()),
|
doPause: () => dispatch(doPause()),
|
||||||
|
doShowOverlay: () => dispatch(doShowOverlay()),
|
||||||
|
doHideOverlay: () => dispatch(doHideOverlay()),
|
||||||
savePosition: (claimId, position) => dispatch(savePosition(claimId, position)),
|
savePosition: (claimId, position) => dispatch(savePosition(claimId, position)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -22,14 +22,9 @@ class VideoPlayer extends React.PureComponent {
|
||||||
this.toggleFullScreenVideo = this.toggleFullScreen.bind(this);
|
this.toggleFullScreenVideo = this.toggleFullScreen.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
const el = this.refs.media.children[0];
|
|
||||||
if (!this.props.paused && nextProps.paused && !el.paused) el.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const container = this.media;
|
const container = this.media;
|
||||||
const { contentType, changeVolume, volume, position, claim } = this.props;
|
const { contentType, changeVolume, volume, position, claim, hiddenControls } = this.props;
|
||||||
|
|
||||||
const loadedMetadata = () => {
|
const loadedMetadata = () => {
|
||||||
this.setState({ hasMetadata: true, startedPlaying: true });
|
this.setState({ hasMetadata: true, startedPlaying: true });
|
||||||
|
@ -48,6 +43,12 @@ class VideoPlayer extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Hide overlay video when the video ends only if its overlayed
|
||||||
|
const ended = () => {
|
||||||
|
this.props.doPause();
|
||||||
|
this.props.savePosition(claim.claim_id, 0);
|
||||||
|
};
|
||||||
|
|
||||||
// use renderAudio override for mp3
|
// use renderAudio override for mp3
|
||||||
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
|
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
|
||||||
this.renderAudio(container, null, false);
|
this.renderAudio(container, null, false);
|
||||||
|
@ -55,7 +56,7 @@ class VideoPlayer extends React.PureComponent {
|
||||||
player.append(
|
player.append(
|
||||||
this.file(),
|
this.file(),
|
||||||
container,
|
container,
|
||||||
{ autoplay: true, controls: true },
|
{ autoplay: true, controls: !hiddenControls },
|
||||||
renderMediaCallback.bind(this)
|
renderMediaCallback.bind(this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -79,16 +80,21 @@ class VideoPlayer extends React.PureComponent {
|
||||||
});
|
});
|
||||||
mediaElement.volume = volume;
|
mediaElement.volume = volume;
|
||||||
mediaElement.addEventListener('dblclick', this.toggleFullScreenVideo);
|
mediaElement.addEventListener('dblclick', this.toggleFullScreenVideo);
|
||||||
|
mediaElement.addEventListener('ended', ended);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(next) {
|
componentWillReceiveProps(next) {
|
||||||
const el = this.media.children[0];
|
const el = this.media.children[0];
|
||||||
if (!this.props.paused && next.paused && !el.paused) el.pause();
|
if (!this.props.paused && next.paused && !el.paused) {
|
||||||
|
el.pause();
|
||||||
|
} else if (this.props.paused && !next.paused && el.paused) {
|
||||||
|
el.play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { contentType, downloadCompleted } = this.props;
|
const { contentType, downloadCompleted, hiddenControls } = this.props;
|
||||||
const { startedPlaying } = this.state;
|
const { startedPlaying } = this.state;
|
||||||
|
|
||||||
if (this.playableType() && !startedPlaying && downloadCompleted) {
|
if (this.playableType() && !startedPlaying && downloadCompleted) {
|
||||||
|
@ -99,7 +105,7 @@ class VideoPlayer extends React.PureComponent {
|
||||||
} else {
|
} else {
|
||||||
player.render(this.file(), container, {
|
player.render(this.file(), container, {
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
controls: true,
|
controls: !hiddenControls,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,7 +117,7 @@ class VideoPlayer extends React.PureComponent {
|
||||||
if (mediaElement) {
|
if (mediaElement) {
|
||||||
mediaElement.removeEventListener('click', this.togglePlayListener);
|
mediaElement.removeEventListener('click', this.togglePlayListener);
|
||||||
}
|
}
|
||||||
this.props.doPause();
|
// this.props.doPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFullScreen(event) {
|
toggleFullScreen(event) {
|
||||||
|
|
|
@ -6,11 +6,11 @@ import type { Claim } from 'types/claim';
|
||||||
import VideoPlayer from './internal/player';
|
import VideoPlayer from './internal/player';
|
||||||
import VideoPlayButton from './internal/play-button';
|
import VideoPlayButton from './internal/play-button';
|
||||||
import LoadingScreen from './internal/loading-screen';
|
import LoadingScreen from './internal/loading-screen';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
const SPACE_BAR_KEYCODE = 32;
|
const SPACE_BAR_KEYCODE = 32;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cancelPlay: () => void,
|
|
||||||
fileInfo: {
|
fileInfo: {
|
||||||
outpoint: string,
|
outpoint: string,
|
||||||
file_name: string,
|
file_name: string,
|
||||||
|
@ -34,12 +34,19 @@ type Props = {
|
||||||
doPlay: () => void,
|
doPlay: () => void,
|
||||||
doPause: () => void,
|
doPause: () => void,
|
||||||
savePosition: (string, number) => void,
|
savePosition: (string, number) => void,
|
||||||
|
doShowOverlay: () => void,
|
||||||
|
doHideOverlay: () => void,
|
||||||
mediaPaused: boolean,
|
mediaPaused: boolean,
|
||||||
mediaPosition: ?number,
|
mediaPosition: ?number,
|
||||||
className: ?string,
|
className: ?string,
|
||||||
obscureNsfw: boolean,
|
obscureNsfw: boolean,
|
||||||
play: string => void,
|
play: string => void,
|
||||||
searchBarFocused: boolean,
|
searchBarFocused: boolean,
|
||||||
|
showOverlay: boolean,
|
||||||
|
hiddenControls: boolean,
|
||||||
|
fromOverlay: boolean,
|
||||||
|
overlayed: boolean,
|
||||||
|
fromOverlay: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Video extends React.PureComponent<Props> {
|
class Video extends React.PureComponent<Props> {
|
||||||
|
@ -53,6 +60,11 @@ class Video extends React.PureComponent<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.handleAutoplay(this.props);
|
this.handleAutoplay(this.props);
|
||||||
window.addEventListener('keydown', this.handleKeyDown);
|
window.addEventListener('keydown', this.handleKeyDown);
|
||||||
|
|
||||||
|
const { showOverlay, doHideOverlay, uri, playingUri } = this.props;
|
||||||
|
if (showOverlay && uri === playingUri) {
|
||||||
|
doHideOverlay();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps: Props) {
|
componentWillReceiveProps(nextProps: Props) {
|
||||||
|
@ -64,13 +76,53 @@ class Video extends React.PureComponent<Props> {
|
||||||
) {
|
) {
|
||||||
this.handleAutoplay(nextProps);
|
this.handleAutoplay(nextProps);
|
||||||
}
|
}
|
||||||
|
if (nextProps.fromOverlay) {
|
||||||
|
this.moveVideoFromOverlayToNormal();
|
||||||
|
this.destroyVideoOnOverlay();
|
||||||
|
this.props.doHideOverlay();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.cancelPlay();
|
const { overlayed, doShowOverlay, mediaPaused } = this.props;
|
||||||
|
if (!overlayed && !mediaPaused) {
|
||||||
|
doShowOverlay();
|
||||||
|
this.moveVideoToOverlay();
|
||||||
|
}
|
||||||
window.removeEventListener('keydown', this.handleKeyDown);
|
window.removeEventListener('keydown', this.handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveVideoToOverlay() {
|
||||||
|
const topContainer = document.getElementById('video__overlay_id_top_container');
|
||||||
|
const container = document.getElementById('video__overlay_id');
|
||||||
|
const videoContainer = this.mediaContainer.media ? this.mediaContainer.media : document.getElementById('insert_video');
|
||||||
|
const video = videoContainer.getElementsByTagName('video')[0];
|
||||||
|
if (video) {
|
||||||
|
topContainer.classList.remove('hiddenContainer');
|
||||||
|
container.appendChild(video);
|
||||||
|
video.controls = false;
|
||||||
|
video.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveVideoFromOverlayToNormal() {
|
||||||
|
const videoContainer = document.getElementById('video__overlay_id');
|
||||||
|
if (!videoContainer) return;
|
||||||
|
const video = videoContainer.getElementsByTagName('video')[0];
|
||||||
|
if (!video) return;
|
||||||
|
const filePageVideoContainer = document.getElementById('insert_video');
|
||||||
|
filePageVideoContainer.appendChild(video);
|
||||||
|
video.controls = true;
|
||||||
|
video.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyVideoOnOverlay() {
|
||||||
|
const topContainer = document.getElementById('video__overlay_id_top_container');
|
||||||
|
const videoContainer = document.getElementById('video__overlay_id');
|
||||||
|
topContainer.classList.add('hiddenContainer');
|
||||||
|
videoContainer.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
handleKeyDown(event: SyntheticKeyboardEvent<*>) {
|
handleKeyDown(event: SyntheticKeyboardEvent<*>) {
|
||||||
const { searchBarFocused } = this.props;
|
const { searchBarFocused } = this.props;
|
||||||
if (!searchBarFocused && event.keyCode === SPACE_BAR_KEYCODE) {
|
if (!searchBarFocused && event.keyCode === SPACE_BAR_KEYCODE) {
|
||||||
|
@ -100,9 +152,17 @@ class Video extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
playContent() {
|
playContent() {
|
||||||
const { play, uri } = this.props;
|
const { play, uri, playingUri, doHideOverlay } = this.props;
|
||||||
|
if (playingUri) {
|
||||||
|
if (playingUri === uri) {
|
||||||
|
this.moveVideoFromOverlayToNormal();
|
||||||
|
}
|
||||||
|
this.destroyVideoOnOverlay();
|
||||||
|
doHideOverlay();
|
||||||
|
} else {
|
||||||
play(uri);
|
play(uri);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
@ -123,6 +183,10 @@ class Video extends React.PureComponent<Props> {
|
||||||
mediaPosition,
|
mediaPosition,
|
||||||
className,
|
className,
|
||||||
obscureNsfw,
|
obscureNsfw,
|
||||||
|
hiddenControls,
|
||||||
|
doHideOverlay,
|
||||||
|
showOverlay,
|
||||||
|
fromOverlay,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const isPlaying = playingUri === uri;
|
const isPlaying = playingUri === uri;
|
||||||
|
@ -147,6 +211,7 @@ class Video extends React.PureComponent<Props> {
|
||||||
const layoverStyle =
|
const layoverStyle =
|
||||||
!shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {};
|
!shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {};
|
||||||
|
|
||||||
|
const commingFromOverlay = playingUri === uri;
|
||||||
return (
|
return (
|
||||||
<div className={classnames('video', {}, className)}>
|
<div className={classnames('video', {}, className)}>
|
||||||
{isPlaying && (
|
{isPlaying && (
|
||||||
|
@ -155,7 +220,7 @@ class Video extends React.PureComponent<Props> {
|
||||||
<div className={layoverClass} style={layoverStyle}>
|
<div className={layoverClass} style={layoverStyle}>
|
||||||
<LoadingScreen status={loadStatusMessage} />
|
<LoadingScreen status={loadStatusMessage} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (commingFromOverlay && fromOverlay ? <div id="insert_video" ref={mediaContainer => this.mediaContainer = mediaContainer} /> :
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
filename={fileInfo.file_name}
|
filename={fileInfo.file_name}
|
||||||
poster={poster}
|
poster={poster}
|
||||||
|
@ -172,6 +237,9 @@ class Video extends React.PureComponent<Props> {
|
||||||
uri={uri}
|
uri={uri}
|
||||||
paused={mediaPaused}
|
paused={mediaPaused}
|
||||||
position={mediaPosition}
|
position={mediaPosition}
|
||||||
|
hiddenControls={hiddenControls}
|
||||||
|
doHideOverlay={doHideOverlay}
|
||||||
|
ref={mediaContainer => this.mediaContainer = mediaContainer }
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
26
src/renderer/component/videoOverlay/index.js
Normal file
26
src/renderer/component/videoOverlay/index.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectPlayingUri } from 'redux/selectors/content';
|
||||||
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
|
import { doPlay, doPause, doHideOverlay } from 'redux/actions/media';
|
||||||
|
import { selectMediaPaused, selectShowOverlay } from 'redux/selectors/media';
|
||||||
|
import VideoOverlay from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
playingUri: selectPlayingUri(state),
|
||||||
|
mediaPaused: selectMediaPaused(state),
|
||||||
|
showOverlay: selectShowOverlay(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
|
doCancelPlay: () => dispatch(doSetPlayingUri(null)),
|
||||||
|
doHideOverlay: () => dispatch(doHideOverlay()),
|
||||||
|
doPlay: () => dispatch(doPlay()),
|
||||||
|
doPause: () => dispatch(doPause()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(VideoOverlay);
|
92
src/renderer/component/videoOverlay/view.jsx
Normal file
92
src/renderer/component/videoOverlay/view.jsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Video from 'component/video';
|
||||||
|
import Overlay from 'component/overlay';
|
||||||
|
import VideoOverlayHeader from 'component/videoOverlayHeader';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import * as icons from 'constants/icons';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
doCancelPlay: () => void,
|
||||||
|
doHideOverlay: () => void,
|
||||||
|
navigate: (string, ?{}) => void,
|
||||||
|
doPlay: () => void,
|
||||||
|
doPause: () => void,
|
||||||
|
playingUri: ?string,
|
||||||
|
mediaPaused: boolean,
|
||||||
|
showOverlay: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
class VideoOverlay extends React.Component<Props> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
(this: any).closeVideo = this.closeVideo.bind(this);
|
||||||
|
(this: any).returnToMedia = this.returnToMedia.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeVideo() {
|
||||||
|
const { doCancelPlay, doHideOverlay } = this.props;
|
||||||
|
doCancelPlay();
|
||||||
|
doHideOverlay();
|
||||||
|
this.destroyMediaPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
returnToMedia() {
|
||||||
|
const { navigate, playingUri, doHideOverlay } = this.props;
|
||||||
|
doHideOverlay();
|
||||||
|
this.destroyMediaPlayer(false);
|
||||||
|
navigate('/show', { uri: playingUri, fromOverlay: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlayOrPauseButton() {
|
||||||
|
const { mediaPaused, doPause, doPlay } = this.props;
|
||||||
|
if (mediaPaused) {
|
||||||
|
return <Button noPadding button="secondary" icon={icons.PLAY} onClick={() => this.getPlayer().play()} />;
|
||||||
|
}
|
||||||
|
return <Button noPadding button="secondary" icon={icons.PAUSE} onClick={() => this.getPlayer().pause()} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayer() {
|
||||||
|
return document.getElementById('video__overlay_id').getElementsByTagName("video")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyMediaPlayer(clearVideo = true){
|
||||||
|
const topContainer = document.getElementById('video__overlay_id_top_container')
|
||||||
|
const videoContainer = document.getElementById('video__overlay_id');
|
||||||
|
topContainer.classList.add('hiddenContainer');
|
||||||
|
if (clearVideo) videoContainer.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { playingUri, showOverlay } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Overlay>
|
||||||
|
{(showOverlay && <VideoOverlayHeader uri={playingUri} onClose={this.closeVideo} />)}
|
||||||
|
|
||||||
|
<div className="video__overlay">
|
||||||
|
{/* <Video className="content__embedded" uri={playingUri} overlayed hiddenControls /> */}
|
||||||
|
{/* <div id="asdf"></div> */}
|
||||||
|
<div className="video content__embedded hiddenContainer" id="video__overlay_id_top_container">
|
||||||
|
<div className="content__view">
|
||||||
|
<div className="content__view--container" id="video__overlay_id">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{(showOverlay && <div className="video__mask" id="video_mask">
|
||||||
|
{this.renderPlayOrPauseButton()}
|
||||||
|
<Button
|
||||||
|
noPadding
|
||||||
|
button="secondary"
|
||||||
|
icon={icons.MAXIMIZE}
|
||||||
|
onClick={() => this.returnToMedia()}
|
||||||
|
/>
|
||||||
|
</div>)}
|
||||||
|
</div>
|
||||||
|
</Overlay>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoOverlay;
|
10
src/renderer/component/videoOverlayHeader/index.js
Normal file
10
src/renderer/component/videoOverlayHeader/index.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectTitleForUri } from 'lbry-redux';
|
||||||
|
import VideoOverlayHeader from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
|
onClose: props.onClose,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, null)(VideoOverlayHeader);
|
31
src/renderer/component/videoOverlayHeader/view.jsx
Normal file
31
src/renderer/component/videoOverlayHeader/view.jsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import * as icons from 'constants/icons';
|
||||||
|
import TruncatedText from 'component/common/truncated-text';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void,
|
||||||
|
title: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
class VideoOverlayHeader extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { onClose, title } = this.props;
|
||||||
|
return (
|
||||||
|
<header className="video_overlay__header">
|
||||||
|
<h4 className="overlay__title--small">
|
||||||
|
<TruncatedText lines={2}>{title}</TruncatedText>
|
||||||
|
</h4>
|
||||||
|
<Button
|
||||||
|
icon={icons.CLOSE}
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoOverlayHeader;
|
|
@ -188,6 +188,9 @@ export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
|
||||||
export const MEDIA_PLAY = 'MEDIA_PLAY';
|
export const MEDIA_PLAY = 'MEDIA_PLAY';
|
||||||
export const MEDIA_PAUSE = 'MEDIA_PAUSE';
|
export const MEDIA_PAUSE = 'MEDIA_PAUSE';
|
||||||
export const MEDIA_POSITION = 'MEDIA_POSITION';
|
export const MEDIA_POSITION = 'MEDIA_POSITION';
|
||||||
|
// Overlay Media
|
||||||
|
export const SHOW_OVERLAY_MEDIA = 'SHOW_OVERLAY_MEDIA';
|
||||||
|
export const HIDE_OVERLAY_MEDIA = 'HIDE_OVERLAY_MEDIA';
|
||||||
|
|
||||||
// Publishing
|
// Publishing
|
||||||
export const CLEAR_PUBLISH = 'CLEAR_PUBLISH';
|
export const CLEAR_PUBLISH = 'CLEAR_PUBLISH';
|
||||||
|
|
|
@ -25,4 +25,7 @@ export const CHECK = 'CheckCircle';
|
||||||
export const HEART = 'Heart';
|
export const HEART = 'Heart';
|
||||||
export const UNLOCK = 'Unlock';
|
export const UNLOCK = 'Unlock';
|
||||||
export const CHECK_SIMPLE = 'Check';
|
export const CHECK_SIMPLE = 'Check';
|
||||||
|
export const PLAY = 'Play';
|
||||||
|
export const MAXIMIZE = 'Maximize2';
|
||||||
|
export const PAUSE = 'Pause';
|
||||||
export const GLOBE = 'Globe';
|
export const GLOBE = 'Globe';
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'scss/all.scss';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
import analytics from './analytics';
|
import analytics from './analytics';
|
||||||
|
import VideoOverlay from './component/videoOverlay/';
|
||||||
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
|
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
|
||||||
|
|
||||||
const { autoUpdater } = remote.require('electron-updater');
|
const { autoUpdater } = remote.require('electron-updater');
|
||||||
|
@ -148,6 +149,7 @@ const init = () => {
|
||||||
<div>
|
<div>
|
||||||
<App />
|
<App />
|
||||||
<SnackBar />
|
<SnackBar />
|
||||||
|
<VideoOverlay />
|
||||||
</div>
|
</div>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
document.getElementById('app')
|
document.getElementById('app')
|
||||||
|
|
|
@ -34,6 +34,7 @@ const select = (state, props) => ({
|
||||||
isPaused: selectMediaPaused(state),
|
isPaused: selectMediaPaused(state),
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||||
|
fromOverlay: Boolean(props.fromOverlay),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -45,6 +45,7 @@ type Props = {
|
||||||
prepareEdit: ({}, string) => void,
|
prepareEdit: ({}, string) => void,
|
||||||
setClientSetting: (string, boolean | string) => void,
|
setClientSetting: (string, boolean | string) => void,
|
||||||
checkSubscription: ({ channelName: string, uri: string }) => void,
|
checkSubscription: ({ channelName: string, uri: string }) => void,
|
||||||
|
fromOverlay: boolean,
|
||||||
subscriptions: Array<Subscription>,
|
subscriptions: Array<Subscription>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,6 +108,7 @@ class FilePage extends React.Component<Props> {
|
||||||
prepareEdit,
|
prepareEdit,
|
||||||
navigate,
|
navigate,
|
||||||
autoplay,
|
autoplay,
|
||||||
|
fromOverlay,
|
||||||
costInfo,
|
costInfo,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -150,7 +152,7 @@ class FilePage extends React.Component<Props> {
|
||||||
</section>
|
</section>
|
||||||
) : (
|
) : (
|
||||||
<section className="card">
|
<section className="card">
|
||||||
{isPlayable && <Video className="content__embedded" uri={uri} />}
|
{isPlayable && <Video className="content__embedded" uri={uri} fromOverlay={fromOverlay} />}
|
||||||
{!isPlayable &&
|
{!isPlayable &&
|
||||||
(thumbnail ? (
|
(thumbnail ? (
|
||||||
<Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} />
|
<Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} />
|
||||||
|
|
|
@ -11,6 +11,7 @@ const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||||
|
fromOverlay: props.fromOverlay,
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -12,6 +12,7 @@ type Props = {
|
||||||
resolveUri: string => void,
|
resolveUri: string => void,
|
||||||
uri: string,
|
uri: string,
|
||||||
claim: Claim,
|
claim: Claim,
|
||||||
|
fromOverlay: boolean,
|
||||||
blackListedOutpoints: Array<{
|
blackListedOutpoints: Array<{
|
||||||
txid: string,
|
txid: string,
|
||||||
nout: number,
|
nout: number,
|
||||||
|
@ -34,7 +35,7 @@ class ShowPage extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { claim, isResolvingUri, uri, blackListedOutpoints } = this.props;
|
const { claim, isResolvingUri, uri, blackListedOutpoints, fromOverlay } = this.props;
|
||||||
|
|
||||||
let innerContent = '';
|
let innerContent = '';
|
||||||
|
|
||||||
|
@ -85,7 +86,7 @@ class ShowPage extends React.PureComponent<Props> {
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
innerContent = <FilePage uri={uri} />;
|
innerContent = <FilePage uri={uri} fromOverlay={fromOverlay} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,3 +26,13 @@ export function savePosition(claimId: String, position: Number) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const doShowOverlay = () => (dispatch: Dispatch) =>
|
||||||
|
dispatch({
|
||||||
|
type: actions.SHOW_OVERLAY_MEDIA,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const doHideOverlay = () => (dispatch: Dispatch) =>
|
||||||
|
dispatch({
|
||||||
|
type: actions.HIDE_OVERLAY_MEDIA,
|
||||||
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as ACTIONS from 'constants/action_types';
|
||||||
|
|
||||||
const reducers = {};
|
const reducers = {};
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
|
showOverlay: false,
|
||||||
playingUri: null,
|
playingUri: null,
|
||||||
currentlyIsPlaying: false,
|
currentlyIsPlaying: false,
|
||||||
rewardedContentClaimIds: [],
|
rewardedContentClaimIds: [],
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as actions from 'constants/action_types';
|
||||||
import { handleActions } from 'util/redux-utils';
|
import { handleActions } from 'util/redux-utils';
|
||||||
|
|
||||||
export type MediaState = {
|
export type MediaState = {
|
||||||
|
showOverlay: Boolean,
|
||||||
paused: Boolean,
|
paused: Boolean,
|
||||||
positions: {
|
positions: {
|
||||||
[string]: number,
|
[string]: number,
|
||||||
|
@ -12,16 +13,17 @@ export type MediaState = {
|
||||||
export type Action = any;
|
export type Action = any;
|
||||||
export type Dispatch = (action: Action) => any;
|
export type Dispatch = (action: Action) => any;
|
||||||
|
|
||||||
const defaultState = { paused: true, positions: {} };
|
const defaultState = { paused: true, positions: {}, showOverlay: false };
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
{
|
{
|
||||||
[actions.MEDIA_PLAY]: (state: MediaState, action: Action) => ({
|
// if parameters: state: MediaState, action: Action
|
||||||
|
[actions.MEDIA_PLAY]: (state: MediaState) => ({
|
||||||
...state,
|
...state,
|
||||||
paused: false,
|
paused: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[actions.MEDIA_PAUSE]: (state: MediaState, action: Action) => ({
|
[actions.MEDIA_PAUSE]: (state: MediaState) => ({
|
||||||
...state,
|
...state,
|
||||||
paused: true,
|
paused: true,
|
||||||
}),
|
}),
|
||||||
|
@ -36,6 +38,16 @@ export default handleActions(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[actions.SHOW_OVERLAY_MEDIA]: (state: MediaState) => ({
|
||||||
|
...state,
|
||||||
|
showOverlay: true,
|
||||||
|
}),
|
||||||
|
|
||||||
|
[actions.HIDE_OVERLAY_MEDIA]: (state: MediaState) => ({
|
||||||
|
...state,
|
||||||
|
showOverlay: false,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
defaultState
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,3 +10,5 @@ export const makeSelectMediaPositionForUri = uri =>
|
||||||
const outpoint = `${claim.txid}:${claim.nout}`;
|
const outpoint = `${claim.txid}:${claim.nout}`;
|
||||||
return state.positions[outpoint] || null;
|
return state.positions[outpoint] || null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const selectShowOverlay = createSelector(selectState, state => state.showOverlay);
|
||||||
|
|
|
@ -24,4 +24,5 @@
|
||||||
@import 'component/_nav.scss';
|
@import 'component/_nav.scss';
|
||||||
@import 'component/_file-list.scss';
|
@import 'component/_file-list.scss';
|
||||||
@import 'component/_search.scss';
|
@import 'component/_search.scss';
|
||||||
|
@import 'component/_overlay.scss';
|
||||||
@import 'component/_toggle.scss';
|
@import 'component/_toggle.scss';
|
||||||
|
|
57
src/renderer/scss/component/_overlay.scss
Normal file
57
src/renderer/scss/component/_overlay.scss
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
.hiddenContainer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
max-height: 50%;
|
||||||
|
max-width: 50%;
|
||||||
|
width: 20%;
|
||||||
|
height: inherit;
|
||||||
|
bottom: 1%;
|
||||||
|
right: 1%;
|
||||||
|
z-index: 3;
|
||||||
|
box-shadow: var(--box-shadow-layer);
|
||||||
|
|
||||||
|
.video__overlay {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.video__mask {
|
||||||
|
opacity: 0;
|
||||||
|
background-color: rgba(0, 0, 8, 0.7);
|
||||||
|
transition: all 0.4s ease-in-out;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .video__mask {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video_overlay__header {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 2rem;
|
||||||
|
background-color: var(--header-primary-color);
|
||||||
|
padding: 20px 10px;
|
||||||
|
|
||||||
|
.overlay__title--small {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
|
}
|
0
src/renderer/scss/component/_video_overlay.scss
Normal file
0
src/renderer/scss/component/_video_overlay.scss
Normal file
Loading…
Reference in a new issue