Skip to content

Commit

Permalink
VideoPress: Add video settings to the VideoPress-enhanced video block (
Browse files Browse the repository at this point in the history
…#13134)

Add the video settings allowing to customize several options for the VideoPress player rendered by the video block (extended by our VideoPress extension for Gutenberg).

Now, after inserting a VideoPress-enhanced video block, a user can set the following settings:
- Autoplay: whether to start playing as soon as the player renders (note that some browsers can prevent a video from being autoplayed).
- Loop: whether to loop the video.
- Muted: whether the video should start in a muted state.
- Playback controls: whether show the controls to allow the user to control video playback.
- Preload: what content is loaded before the video is played:
  - None: Indicates that the video should not be preloaded.
  - Metadata: Indicates that only video metadata (e.g. length) is fetched.
  - Auto: Indicates that the whole video file can be downloaded, even if the user is not expected to use it.
- Poster image: image to be shown while the video is downloading.

Given that these settings modifies the URL saved in the block content, any existing post containing a VideoPress-enhanced video block would result in a "invalid content" error. In order to support those posts, a deprecated version of the block has been added.

These changes also introduces a new `playsInline` attribute (added in core in WordPress/gutenberg#14500), but it's currently unmodifiable, since VideoPress doesn't support it yet.
  • Loading branch information
mmtr authored Aug 7, 2019
1 parent a2c2988 commit 57ed23f
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 34 deletions.
48 changes: 48 additions & 0 deletions extensions/blocks/videopress/deprecated/v1/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Internal dependencies
*/
import save from './save';

export default {
attributes: {
autoplay: {
type: 'boolean',
},
caption: {
type: 'string',
source: 'html',
selector: 'figcaption',
},
controls: {
type: 'boolean',
default: true,
},
guid: {
type: 'string',
},
id: {
type: 'number',
},
loop: {
type: 'boolean',
},
muted: {
type: 'boolean',
},
poster: {
type: 'string',
},
preload: {
type: 'string',
default: 'metadata',
},
src: {
type: 'string',
},
},
support: {
reusable: false,
},
save,
isDeprecation: true,
};
25 changes: 25 additions & 0 deletions extensions/blocks/videopress/deprecated/v1/save.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* External dependencies
*/
import { RichText } from '@wordpress/editor';

export default function VideoPressSave( { attributes } ) {
const { caption, guid } = attributes;

if ( ! guid ) {
return null;
}

const url = `https://videopress.com/v/${ guid }`;

return (
<figure className="wp-block-embed is-type-video is-provider-videopress">
<div className="wp-block-embed__wrapper">
{ `\n${ url }\n` /* URL needs to be on its own line. */ }
</div>
{ ! RichText.isEmpty( caption ) && (
<RichText.Content tagName="figcaption" value={ caption } />
) }
</figure>
);
}
201 changes: 177 additions & 24 deletions extensions/blocks/videopress/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,39 @@
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import classnames from 'classnames';
import { __ } from '@wordpress/i18n';
import { BlockControls, RichText } from '@wordpress/editor';
import { isBlobURL } from '@wordpress/blob';
import {
BaseControl,
Button,
Disabled,
IconButton,
PanelBody,
SandBox,
SelectControl,
ToggleControl,
Toolbar,
} from '@wordpress/components';
import { compose, createHigherOrderComponent, withInstanceId } from '@wordpress/compose';
import { withDispatch, withSelect } from '@wordpress/data';
import {
BlockControls,
InspectorControls,
MediaUpload,
MediaUploadCheck,
RichText,
} from '@wordpress/editor';
import { Component, createRef, Fragment } from '@wordpress/element';
import { compose, createHigherOrderComponent } from '@wordpress/compose';
import { Disabled, IconButton, SandBox, Toolbar } from '@wordpress/components';
import { __, _x, sprintf } from '@wordpress/i18n';
import classnames from 'classnames';
import { get } from 'lodash';
import { isBlobURL } from '@wordpress/blob';
import { withSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import Loading from './loading';
import { getVideoPressUrl } from './url';

const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ];

const VideoPressEdit = CoreVideoEdit =>
class extends Component {
Expand All @@ -37,11 +56,19 @@ const VideoPressEdit = CoreVideoEdit =>
}

componentDidUpdate( prevProps ) {
const { attributes } = this.props;
const { attributes, invalidateCachedEmbedPreview, url } = this.props;

if ( attributes.id !== prevProps.attributes.id ) {
this.setGuid();
}

if ( url && url !== prevProps.url ) {
// Due to a current bug in Gutenberg (https://github.com/WordPress/gutenberg/issues/16831), the
// `SandBox` component is not rendered again when the injected `html` prop changes. To work around that,
// we invalidate the cached preview of the embed VideoPress player in order to force the rendering of a
// new instance of the `SandBox` component that ensures the injected `html` will be rendered.
invalidateCachedEmbedPreview( url );
}
}

fallbackToCore = () => {
Expand Down Expand Up @@ -90,52 +117,161 @@ const VideoPressEdit = CoreVideoEdit =>
} );
};

onSelectPoster = image => {
const { setAttributes } = this.props;
setAttributes( { poster: image.url } );
};

onRemovePoster = () => {
this.props.setAttributes( { poster: '' } );
const { setAttributes } = this.props;
setAttributes( { poster: '' } );

// Move focus back to the Media Upload button.
this.posterImageButton.current.focus();
};

toggleAttribute = attribute => {
return newValue => {
this.props.setAttributes( { [ attribute ]: newValue } );
};
};

getAutoplayHelp = checked => {
return checked
? __( 'Note: Autoplaying videos may cause usability issues for some visitors.', 'jetpack' )
: null;
};

render() {
const {
attributes,
className,
instanceId,
isFetchingPreview,
isSelected,
isUploading,
preview,
setAttributes,
} = this.props;
const { fallback, isFetchingMedia } = this.state;
const { autoplay, caption, controls, loop, muted, poster, preload } = attributes;

const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`;

const blockSettings = (
<Fragment>
<BlockControls>
<Toolbar>
<IconButton
className="components-icon-button components-toolbar__control"
label={ __( 'Edit video', 'jetpack' ) }
onClick={ this.switchToEditing }
icon="edit"
/>
</Toolbar>
</BlockControls>
<InspectorControls>
<PanelBody title={ __( 'Video Settings', 'jetpack' ) }>
<ToggleControl
label={ __( 'Autoplay', 'jetpack' ) }
onChange={ this.toggleAttribute( 'autoplay' ) }
checked={ autoplay }
help={ this.getAutoplayHelp }
/>
<ToggleControl
label={ __( 'Loop', 'jetpack' ) }
onChange={ this.toggleAttribute( 'loop' ) }
checked={ loop }
/>
<ToggleControl
label={ __( 'Muted', 'jetpack' ) }
onChange={ this.toggleAttribute( 'muted' ) }
checked={ muted }
/>
<ToggleControl
label={ __( 'Playback Controls', 'jetpack' ) }
onChange={ this.toggleAttribute( 'controls' ) }
checked={ controls }
/>
<SelectControl
label={ __( 'Preload', 'jetpack' ) }
value={ preload }
onChange={ value => setAttributes( { preload: value } ) }
options={ [
{ value: 'auto', label: _x( 'Auto', 'VideoPress preload setting', 'jetpack' ) },
{
value: 'metadata',
label: _x( 'Metadata', 'VideoPress preload setting', 'jetpack' ),
},
{ value: 'none', label: _x( 'None', 'VideoPress preload setting', 'jetpack' ) },
] }
/>
<MediaUploadCheck>
<BaseControl
className="editor-video-poster-control"
label={ __( 'Poster Image', 'jetpack' ) }
>
<MediaUpload
title={ __( 'Select Poster Image', 'jetpack' ) }
onSelect={ this.onSelectPoster }
allowedTypes={ VIDEO_POSTER_ALLOWED_MEDIA_TYPES }
render={ ( { open } ) => (
<Button
isDefault
onClick={ open }
ref={ this.posterImageButton }
aria-describedby={ videoPosterDescription }
>
{ ! poster
? __( 'Select Poster Image', 'jetpack' )
: __( 'Replace image', 'jetpack' ) }
</Button>
) }
/>
<p id={ videoPosterDescription } hidden>
{ poster
? sprintf( __( 'The current poster image url is %s', 'jetpack' ), poster )
: __( 'There is no poster image currently selected', 'jetpack' ) }
</p>
{ !! poster && (
<Button onClick={ this.onRemovePoster } isLink isDestructive>
{ __( 'Remove Poster Image' ) }
</Button>
) }
</BaseControl>
</MediaUploadCheck>
</PanelBody>
</InspectorControls>
</Fragment>
);

if ( isUploading ) {
return <Loading text={ __( 'Uploading…', 'jetpack' ) } />;
return (
<Fragment>
{ blockSettings }
<Loading text={ __( 'Uploading…', 'jetpack' ) } />
</Fragment>
);
}

if ( isFetchingMedia || isFetchingPreview ) {
return <Loading text={ __( 'Embedding…', 'jetpack' ) } />;
return (
<Fragment>
{ blockSettings }
<Loading text={ __( 'Generating preview…', 'jetpack' ) } />
</Fragment>
);
}

if ( fallback || ! preview ) {
return <CoreVideoEdit { ...this.props } />;
}

const { html, scripts } = preview;
const { caption } = attributes;

return (
<Fragment>
<BlockControls>
<Toolbar>
<IconButton
className="components-icon-button components-toolbar__control"
label={ __( 'Edit video', 'jetpack' ) }
onClick={ this.switchToEditing }
icon="edit"
/>
</Toolbar>
</BlockControls>
{ blockSettings }
<figure className={ classnames( className, 'wp-block-embed', 'is-type-video' ) }>
{ /*
Disable the video player so the user clicking on it won't play the
Expand Down Expand Up @@ -164,10 +300,17 @@ const VideoPressEdit = CoreVideoEdit =>
export default createHigherOrderComponent(
compose( [
withSelect( ( select, ownProps ) => {
const { guid, src } = ownProps.attributes;
const { autoplay, controls, guid, loop, muted, poster, preload, src } = ownProps.attributes;
const { getEmbedPreview, isRequestingEmbedPreview } = select( 'core' );

const url = !! guid && `https://videopress.com/v/${ guid }`;
const url = getVideoPressUrl( guid, {
autoplay,
controls,
loop,
muted,
poster,
preload,
} );
const preview = !! url && getEmbedPreview( url );

const isFetchingEmbedPreview = !! url && isRequestingEmbedPreview( url );
Expand All @@ -177,8 +320,18 @@ export default createHigherOrderComponent(
isFetchingPreview: isFetchingEmbedPreview,
isUploading,
preview,
url,
};
} ),
withDispatch( dispatch => {
const invalidateCachedEmbedPreview = url => {
dispatch( 'core/data' ).invalidateResolution( 'core', 'getEmbedPreview', [ url ] );
};
return {
invalidateCachedEmbedPreview,
};
} ),
withInstanceId,
VideoPressEdit,
] ),
'withVideoPressEdit'
Expand Down
Loading

0 comments on commit 57ed23f

Please sign in to comment.