Merge branch 'master' into should-component-update
This commit is contained in:
commit
f21bf287d9
7 changed files with 226 additions and 148 deletions
|
@ -9,7 +9,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
* More file types, like audio and documents, can be streamed and/or served from the app
|
* More file types, like audio and documents, can be streamed and/or served from the app
|
||||||
*
|
* Videos now have a classy loading spinner
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* All UI strings are now rendered according to gettext standard, in prep for i18n
|
* All UI strings are now rendered according to gettext standard, in prep for i18n
|
||||||
|
|
14
ui/js/component/video/internal/loading-screen.jsx
Normal file
14
ui/js/component/video/internal/loading-screen.jsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const LoadingScreen = ({ status }) =>
|
||||||
|
<div className="video__loading-screen">
|
||||||
|
<div>
|
||||||
|
<div className="video__loading-spinner" />
|
||||||
|
|
||||||
|
<div className="video__loading-status">
|
||||||
|
{status}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
export default LoadingScreen;
|
92
ui/js/component/video/internal/play-button.jsx
Normal file
92
ui/js/component/video/internal/play-button.jsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import React from "react";
|
||||||
|
import FilePrice from "component/filePrice";
|
||||||
|
import Link from "component/link";
|
||||||
|
import Modal from "component/modal";
|
||||||
|
|
||||||
|
class VideoPlayButton extends React.Component {
|
||||||
|
onPurchaseConfirmed() {
|
||||||
|
this.props.closeModal();
|
||||||
|
this.props.startPlaying();
|
||||||
|
this.props.loadVideo(this.props.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWatchClick() {
|
||||||
|
this.props.purchaseUri(this.props.uri).then(() => {
|
||||||
|
if (!this.props.modal) {
|
||||||
|
this.props.startPlaying();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
button,
|
||||||
|
label,
|
||||||
|
metadata,
|
||||||
|
metadata: { title },
|
||||||
|
uri,
|
||||||
|
modal,
|
||||||
|
closeModal,
|
||||||
|
isLoading,
|
||||||
|
costInfo,
|
||||||
|
fileInfo,
|
||||||
|
mediaType,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
/*
|
||||||
|
title={
|
||||||
|
isLoading ? "Video is Loading" :
|
||||||
|
!costInfo ? "Waiting on cost info..." :
|
||||||
|
fileInfo === undefined ? "Waiting on file info..." : ""
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const disabled =
|
||||||
|
isLoading ||
|
||||||
|
fileInfo === undefined ||
|
||||||
|
(fileInfo === null && (!costInfo || costInfo.cost === undefined));
|
||||||
|
const icon = ["audio", "video"].indexOf(mediaType) !== -1
|
||||||
|
? "icon-play"
|
||||||
|
: "icon-folder-o";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
button={button ? button : null}
|
||||||
|
disabled={disabled}
|
||||||
|
label={label ? label : ""}
|
||||||
|
className="video__play-button"
|
||||||
|
icon={icon}
|
||||||
|
onClick={this.onWatchClick.bind(this)}
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
contentLabel={__("Not enough credits")}
|
||||||
|
isOpen={modal == "notEnoughCredits"}
|
||||||
|
onConfirmed={closeModal}
|
||||||
|
>
|
||||||
|
{__("You don't have enough LBRY credits to pay for this stream.")}
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
type="confirm"
|
||||||
|
isOpen={modal == "affirmPurchaseAndPlay"}
|
||||||
|
contentLabel={__("Confirm Purchase")}
|
||||||
|
onConfirmed={this.onPurchaseConfirmed.bind(this)}
|
||||||
|
onAborted={closeModal}
|
||||||
|
>
|
||||||
|
{__("This will purchase")} <strong>{title}</strong> {__("for")}
|
||||||
|
{" "}<strong><FilePrice uri={uri} look="plain" /></strong>
|
||||||
|
{" "}{__("credits")}.
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
isOpen={modal == "timedOut"}
|
||||||
|
onConfirmed={closeModal}
|
||||||
|
contentLabel={__("Timed Out")}
|
||||||
|
>
|
||||||
|
{__("Sorry, your download timed out :(")}
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoPlayButton;
|
35
ui/js/component/video/internal/player.jsx
Normal file
35
ui/js/component/video/internal/player.jsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Thumbnail } from "component/common";
|
||||||
|
import player from "render-media";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
class VideoPlayer extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
const elem = this.refs.media;
|
||||||
|
const { downloadPath, filename } = this.props;
|
||||||
|
const file = {
|
||||||
|
name: filename,
|
||||||
|
createReadStream: opts => {
|
||||||
|
return fs.createReadStream(downloadPath, opts);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
player.append(file, elem, {
|
||||||
|
autoplay: true,
|
||||||
|
controls: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { downloadPath, mediaType, poster } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{["audio", "application"].indexOf(mediaType) !== -1 &&
|
||||||
|
<Thumbnail src={poster} className="video-embedded" />}
|
||||||
|
<div ref="media" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoPlayer;
|
|
@ -1,96 +1,8 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import FilePrice from "component/filePrice";
|
|
||||||
import Link from "component/link";
|
|
||||||
import Modal from "component/modal";
|
|
||||||
import lbry from "lbry";
|
import lbry from "lbry";
|
||||||
import { Thumbnail } from "component/common";
|
import VideoPlayer from "./internal/player";
|
||||||
|
import VideoPlayButton from "./internal/play-button";
|
||||||
class VideoPlayButton extends React.PureComponent {
|
import LoadingScreen from "./internal/loading-screen";
|
||||||
onPurchaseConfirmed() {
|
|
||||||
this.props.closeModal();
|
|
||||||
this.props.startPlaying();
|
|
||||||
this.props.loadVideo(this.props.uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
onWatchClick() {
|
|
||||||
this.props.purchaseUri(this.props.uri).then(() => {
|
|
||||||
if (!this.props.modal) {
|
|
||||||
this.props.startPlaying();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
button,
|
|
||||||
label,
|
|
||||||
className,
|
|
||||||
metadata,
|
|
||||||
metadata: { title },
|
|
||||||
uri,
|
|
||||||
modal,
|
|
||||||
closeModal,
|
|
||||||
isLoading,
|
|
||||||
costInfo,
|
|
||||||
fileInfo,
|
|
||||||
mediaType,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
/*
|
|
||||||
title={
|
|
||||||
isLoading ? "Video is Loading" :
|
|
||||||
!costInfo ? "Waiting on cost info..." :
|
|
||||||
fileInfo === undefined ? "Waiting on file info..." : ""
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const disabled =
|
|
||||||
isLoading ||
|
|
||||||
fileInfo === undefined ||
|
|
||||||
(fileInfo === null && (!costInfo || costInfo.cost === undefined));
|
|
||||||
const icon = ["audio", "video"].indexOf(mediaType) !== -1
|
|
||||||
? "icon-play"
|
|
||||||
: "icon-folder-o";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Link
|
|
||||||
button={button ? button : null}
|
|
||||||
disabled={disabled}
|
|
||||||
label={label ? label : ""}
|
|
||||||
className="video__play-button"
|
|
||||||
icon={icon}
|
|
||||||
onClick={this.onWatchClick.bind(this)}
|
|
||||||
/>
|
|
||||||
<Modal
|
|
||||||
contentLabel={__("Not enough credits")}
|
|
||||||
isOpen={modal == "notEnoughCredits"}
|
|
||||||
onConfirmed={closeModal}
|
|
||||||
>
|
|
||||||
{__("You don't have enough LBRY credits to pay for this stream.")}
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
type="confirm"
|
|
||||||
isOpen={modal == "affirmPurchaseAndPlay"}
|
|
||||||
contentLabel={__("Confirm Purchase")}
|
|
||||||
onConfirmed={this.onPurchaseConfirmed.bind(this)}
|
|
||||||
onAborted={closeModal}
|
|
||||||
>
|
|
||||||
{__("This will purchase")} <strong>{title}</strong> {__("for")}
|
|
||||||
{" "}<strong><FilePrice uri={uri} look="plain" /></strong>
|
|
||||||
{" "}{__("credits")}.
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
isOpen={modal == "timedOut"}
|
|
||||||
onConfirmed={closeModal}
|
|
||||||
contentLabel={__("Timed Out")}
|
|
||||||
>
|
|
||||||
{__("Sorry, your download timed out :(")}
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Video extends React.PureComponent {
|
class Video extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -148,64 +60,26 @@ class Video extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={klassName}>
|
<div className={klassName}>
|
||||||
{isPlaying
|
{isPlaying &&
|
||||||
? !isReadyToPlay
|
(!isReadyToPlay
|
||||||
? <span>
|
? <LoadingScreen status={loadStatusMessage} />
|
||||||
{__(
|
|
||||||
"this is the world's worst loading screen and we shipped our software with it anyway..."
|
|
||||||
)}
|
|
||||||
{" "}<br /><br />{loadStatusMessage}
|
|
||||||
</span>
|
|
||||||
: <VideoPlayer
|
: <VideoPlayer
|
||||||
filename={fileInfo.file_name}
|
filename={fileInfo.file_name}
|
||||||
poster={poster}
|
poster={poster}
|
||||||
downloadPath={fileInfo.download_path}
|
downloadPath={fileInfo.download_path}
|
||||||
mediaType={mediaType}
|
mediaType={mediaType}
|
||||||
poster={poster}
|
/>)}
|
||||||
/>
|
{!isPlaying &&
|
||||||
: <div
|
<div
|
||||||
className="video__cover"
|
className="video__cover"
|
||||||
style={{ backgroundImage: 'url("' + metadata.thumbnail + '")' }}
|
style={{ backgroundImage: 'url("' + metadata.thumbnail + '")' }}
|
||||||
>
|
>
|
||||||
<VideoPlayButton
|
<VideoPlayButton
|
||||||
startPlaying={this.startPlaying.bind(this)}
|
startPlaying={this.startPlaying.bind(this)}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
mediaType={mediaType}
|
mediaType={mediaType}
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const from = require("from2");
|
|
||||||
const player = require("render-media");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
class VideoPlayer extends React.PureComponent {
|
|
||||||
componentDidMount() {
|
|
||||||
const elem = this.refs.media;
|
|
||||||
const { downloadPath, filename } = this.props;
|
|
||||||
const file = {
|
|
||||||
name: filename,
|
|
||||||
createReadStream: opts => {
|
|
||||||
return fs.createReadStream(downloadPath, opts);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
player.append(file, elem, {
|
|
||||||
autoplay: true,
|
|
||||||
controls: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { downloadPath, mediaType, poster } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{["audio", "application"].indexOf(mediaType) !== -1 &&
|
|
||||||
<Thumbnail src={poster} className="video-embedded" />}
|
|
||||||
<div ref="media" />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,7 +373,7 @@ class PublishPage extends React.PureComponent {
|
||||||
lbry
|
lbry
|
||||||
.channel_new({
|
.channel_new({
|
||||||
channel_name: newChannelName,
|
channel_name: newChannelName,
|
||||||
amount: parseInt(this.state.newChannelBid),
|
amount: parseFloat(this.state.newChannelBid),
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
|
@ -594,6 +594,7 @@ class PublishPage extends React.PureComponent {
|
||||||
className="form-field__input--inline"
|
className="form-field__input--inline"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
placeholder="1.00"
|
placeholder="1.00"
|
||||||
|
min="0.01"
|
||||||
onChange={event => this.handleFeeAmountChange(event)}
|
onChange={event => this.handleFeeAmountChange(event)}
|
||||||
/>
|
/>
|
||||||
{" "}
|
{" "}
|
||||||
|
@ -752,6 +753,7 @@ class PublishPage extends React.PureComponent {
|
||||||
label={__("Deposit")}
|
label={__("Deposit")}
|
||||||
postfix="LBC"
|
postfix="LBC"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
|
min="0"
|
||||||
type="number"
|
type="number"
|
||||||
helper={lbcInputHelp}
|
helper={lbcInputHelp}
|
||||||
onChange={event => {
|
onChange={event => {
|
||||||
|
|
|
@ -13,7 +13,6 @@ video {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.video-embedded {
|
.video-embedded {
|
||||||
max-width: $width-page-constrained;
|
max-width: $width-page-constrained;
|
||||||
max-height: $height-video-embedded;
|
max-height: $height-video-embedded;
|
||||||
|
@ -28,12 +27,73 @@ video {
|
||||||
&.video--active {
|
&.video--active {
|
||||||
/*background: none;*/
|
/*background: none;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr {
|
.plyr {
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.video__loading-screen {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video__loading-spinner {
|
||||||
|
position: relative;
|
||||||
|
width: 11em;
|
||||||
|
height: 11em;
|
||||||
|
margin: 20px auto;
|
||||||
|
font-size: 3px;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 50%);
|
||||||
|
animation: spin 1.4s infinite linear;
|
||||||
|
transform: translateZ(0);
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 100% 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
height: 75%;
|
||||||
|
width: 75%;
|
||||||
|
margin: auto;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background: black;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video__loading-status {
|
||||||
|
padding-top: 20px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.video__cover {
|
.video__cover {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -44,6 +104,7 @@ video {
|
||||||
position: relative;
|
position: relative;
|
||||||
.video__play-button { @include absolute-center(); }
|
.video__play-button { @include absolute-center(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.video__play-button {
|
.video__play-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
Loading…
Reference in a new issue