mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-09 13:28:29 +01:00
Add Videos to Remarkup
Summary: Ref T6916. Added video to remarkup using D7156 as reference. Test Plan: - Viewed video files (MP4, Ogg) in Safari, Chrome, Firefox (some don't work, e.g., OGG in Safari, but nothing we can really do about that). - Used `alt`. - Used `autoplay`. - Used `loop`. - Used `media=audio`. - Viewed file detail page. Reviewers: nateguchi2, chad, #blessed_reviewers Reviewed By: chad, #blessed_reviewers Subscribers: asherkin, ivo, joshuaspence, Korvin, epriestley Tags: #remarkup Maniphest Tasks: T6916 Differential Revision: https://secure.phabricator.com/D11297
This commit is contained in:
parent
f0eb6f4fe0
commit
411cf13457
7 changed files with 173 additions and 30 deletions
|
@ -34,9 +34,16 @@ final class PhabricatorFilesConfigOptions
|
||||||
'image/x-icon' => 'image/x-icon',
|
'image/x-icon' => 'image/x-icon',
|
||||||
'image/vnd.microsoft.icon' => 'image/x-icon',
|
'image/vnd.microsoft.icon' => 'image/x-icon',
|
||||||
|
|
||||||
'audio/x-wav' => 'audio/x-wav',
|
// This is a generic type for both OGG video and OGG audio.
|
||||||
'application/ogg' => 'application/ogg',
|
'application/ogg' => 'application/ogg',
|
||||||
|
|
||||||
|
'audio/x-wav' => 'audio/x-wav',
|
||||||
'audio/mpeg' => 'audio/mpeg',
|
'audio/mpeg' => 'audio/mpeg',
|
||||||
|
'audio/ogg' => 'audio/ogg',
|
||||||
|
|
||||||
|
'video/mp4' => 'video/mp4',
|
||||||
|
'video/ogg' => 'video/ogg',
|
||||||
|
'video/webm' => 'video/webm',
|
||||||
);
|
);
|
||||||
|
|
||||||
$image_default = array(
|
$image_default = array(
|
||||||
|
@ -49,10 +56,29 @@ final class PhabricatorFilesConfigOptions
|
||||||
'image/vnd.microsoft.icon' => true,
|
'image/vnd.microsoft.icon' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// The "application/ogg" type is listed as both an audio and video type,
|
||||||
|
// because it may contain either type of content.
|
||||||
|
|
||||||
$audio_default = array(
|
$audio_default = array(
|
||||||
'audio/x-wav' => true,
|
'audio/x-wav' => true,
|
||||||
'application/ogg' => true,
|
|
||||||
'audio/mpeg' => true,
|
'audio/mpeg' => true,
|
||||||
|
'audio/ogg' => true,
|
||||||
|
|
||||||
|
// These are video or ambiguous types, but can be forced to render as
|
||||||
|
// audio with `media=audio`, which seems to work properly in browsers.
|
||||||
|
// (For example, you can embed a music video as audio if you just want
|
||||||
|
// to set the mood for your task without distracting viewers.)
|
||||||
|
'video/mp4' => true,
|
||||||
|
'video/ogg' => true,
|
||||||
|
'application/ogg' => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
$video_default = array(
|
||||||
|
'video/mp4' => true,
|
||||||
|
'video/ogg' => true,
|
||||||
|
'video/webm' => true,
|
||||||
|
'application/ogg' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// largely lifted from http://en.wikipedia.org/wiki/Internet_media_type
|
// largely lifted from http://en.wikipedia.org/wiki/Internet_media_type
|
||||||
|
@ -70,6 +96,7 @@ final class PhabricatorFilesConfigOptions
|
||||||
// movie file icon
|
// movie file icon
|
||||||
'video/mpeg' => 'fa-file-movie-o',
|
'video/mpeg' => 'fa-file-movie-o',
|
||||||
'video/mp4' => 'fa-file-movie-o',
|
'video/mp4' => 'fa-file-movie-o',
|
||||||
|
'application/ogg' => 'fa-file-movie-o',
|
||||||
'video/ogg' => 'fa-file-movie-o',
|
'video/ogg' => 'fa-file-movie-o',
|
||||||
'video/quicktime' => 'fa-file-movie-o',
|
'video/quicktime' => 'fa-file-movie-o',
|
||||||
'video/webm' => 'fa-file-movie-o',
|
'video/webm' => 'fa-file-movie-o',
|
||||||
|
@ -122,8 +149,14 @@ final class PhabricatorFilesConfigOptions
|
||||||
->setSummary(pht('Configure which MIME types are audio.'))
|
->setSummary(pht('Configure which MIME types are audio.'))
|
||||||
->setDescription(
|
->setDescription(
|
||||||
pht(
|
pht(
|
||||||
'List of MIME types which can be used to render an `%s` tag.',
|
'List of MIME types which can be rendered with an `%s` tag.',
|
||||||
'<audio />')),
|
'<audio />')),
|
||||||
|
$this->newOption('files.video-mime-types', 'set', $video_default)
|
||||||
|
->setSummary(pht('Configure which MIME types are video.'))
|
||||||
|
->setDescription(
|
||||||
|
pht(
|
||||||
|
'List of MIME types which can be rendered with a `%s` tag.',
|
||||||
|
'<video />')),
|
||||||
$this->newOption('files.icon-mime-types', 'wild', $icon_default)
|
$this->newOption('files.icon-mime-types', 'wild', $icon_default)
|
||||||
->setLocked(true)
|
->setLocked(true)
|
||||||
->setSummary(pht('Configure which MIME types map to which icons.'))
|
->setSummary(pht('Configure which MIME types map to which icons.'))
|
||||||
|
|
|
@ -230,23 +230,34 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
|
||||||
$cache_string = pht('Not Applicable');
|
$cache_string = pht('Not Applicable');
|
||||||
}
|
}
|
||||||
|
|
||||||
$finfo->addProperty(pht('Viewable Image'), $image_string);
|
$types = array();
|
||||||
$finfo->addProperty(pht('Cacheable'), $cache_string);
|
if ($file->isViewableImage()) {
|
||||||
|
$types[] = pht('Image');
|
||||||
$builtin = $file->getBuiltinName();
|
|
||||||
if ($builtin === null) {
|
|
||||||
$builtin_string = pht('No');
|
|
||||||
} else {
|
|
||||||
$builtin_string = $builtin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$finfo->addProperty(pht('Builtin'), $builtin_string);
|
if ($file->isVideo()) {
|
||||||
|
$types[] = pht('Video');
|
||||||
|
}
|
||||||
|
|
||||||
$is_profile = $file->getIsProfileImage()
|
if ($file->isAudio()) {
|
||||||
? pht('Yes')
|
$types[] = pht('Audio');
|
||||||
: pht('No');
|
}
|
||||||
|
|
||||||
$finfo->addProperty(pht('Profile'), $is_profile);
|
if ($file->getCanCDN()) {
|
||||||
|
$types[] = pht('Can CDN');
|
||||||
|
}
|
||||||
|
|
||||||
|
$builtin = $file->getBuiltinName();
|
||||||
|
if ($builtin !== null) {
|
||||||
|
$types[] = pht('Builtin ("%s")', $builtin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file->getIsProfileImage()) {
|
||||||
|
$types[] = pht('Profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
$types = implode(', ', $types);
|
||||||
|
$finfo->addProperty(pht('Attributes'), $types);
|
||||||
|
|
||||||
$storage_properties = new PHUIPropertyListView();
|
$storage_properties = new PHUIPropertyListView();
|
||||||
$box->addPropertyList($storage_properties, pht('Storage'));
|
$box->addPropertyList($storage_properties, pht('Storage'));
|
||||||
|
@ -292,6 +303,23 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
|
||||||
$media = id(new PHUIPropertyListView())
|
$media = id(new PHUIPropertyListView())
|
||||||
->addImageContent($linked_image);
|
->addImageContent($linked_image);
|
||||||
|
|
||||||
|
$box->addPropertyList($media);
|
||||||
|
} else if ($file->isVideo()) {
|
||||||
|
$video = phutil_tag(
|
||||||
|
'video',
|
||||||
|
array(
|
||||||
|
'controls' => 'controls',
|
||||||
|
'class' => 'phui-property-list-video',
|
||||||
|
),
|
||||||
|
phutil_tag(
|
||||||
|
'source',
|
||||||
|
array(
|
||||||
|
'src' => $file->getViewURI(),
|
||||||
|
'type' => $file->getMimeType(),
|
||||||
|
)));
|
||||||
|
$media = id(new PHUIPropertyListView())
|
||||||
|
->addImageContent($video);
|
||||||
|
|
||||||
$box->addPropertyList($media);
|
$box->addPropertyList($media);
|
||||||
} else if ($file->isAudio()) {
|
} else if ($file->isAudio()) {
|
||||||
$audio = phutil_tag(
|
$audio = phutil_tag(
|
||||||
|
|
|
@ -43,12 +43,27 @@ final class PhabricatorEmbedFileRemarkupRule
|
||||||
|
|
||||||
$is_viewable_image = $object->isViewableImage();
|
$is_viewable_image = $object->isViewableImage();
|
||||||
$is_audio = $object->isAudio();
|
$is_audio = $object->isAudio();
|
||||||
|
$is_video = $object->isVideo();
|
||||||
$force_link = ($options['layout'] == 'link');
|
$force_link = ($options['layout'] == 'link');
|
||||||
|
|
||||||
$options['viewable'] = ($is_viewable_image || $is_audio);
|
// If a file is both audio and video, as with "application/ogg" by default,
|
||||||
|
// render it as video but allow the user to specify `media=audio` if they
|
||||||
|
// want to force it to render as audio.
|
||||||
|
if ($is_audio && $is_video) {
|
||||||
|
$media = $options['media'];
|
||||||
|
if ($media == 'audio') {
|
||||||
|
$is_video = false;
|
||||||
|
} else {
|
||||||
|
$is_audio = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$options['viewable'] = ($is_viewable_image || $is_audio || $is_video);
|
||||||
|
|
||||||
if ($is_viewable_image && !$force_link) {
|
if ($is_viewable_image && !$force_link) {
|
||||||
return $this->renderImageFile($object, $handle, $options);
|
return $this->renderImageFile($object, $handle, $options);
|
||||||
|
} else if ($is_video && !$force_link) {
|
||||||
|
return $this->renderVideoFile($object, $handle, $options);
|
||||||
} else if ($is_audio && !$force_link) {
|
} else if ($is_audio && !$force_link) {
|
||||||
return $this->renderAudioFile($object, $handle, $options);
|
return $this->renderAudioFile($object, $handle, $options);
|
||||||
} else {
|
} else {
|
||||||
|
@ -64,6 +79,9 @@ final class PhabricatorEmbedFileRemarkupRule
|
||||||
'width' => null,
|
'width' => null,
|
||||||
'height' => null,
|
'height' => null,
|
||||||
'alt' => null,
|
'alt' => null,
|
||||||
|
'media' => null,
|
||||||
|
'autoplay' => null,
|
||||||
|
'loop' => null,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($option_string) {
|
if ($option_string) {
|
||||||
|
@ -201,22 +219,47 @@ final class PhabricatorEmbedFileRemarkupRule
|
||||||
PhabricatorFile $file,
|
PhabricatorFile $file,
|
||||||
PhabricatorObjectHandle $handle,
|
PhabricatorObjectHandle $handle,
|
||||||
array $options) {
|
array $options) {
|
||||||
|
return $this->renderMediaFile('audio', $file, $handle, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderVideoFile(
|
||||||
|
PhabricatorFile $file,
|
||||||
|
PhabricatorObjectHandle $handle,
|
||||||
|
array $options) {
|
||||||
|
return $this->renderMediaFile('video', $file, $handle, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderMediaFile(
|
||||||
|
$tag,
|
||||||
|
PhabricatorFile $file,
|
||||||
|
PhabricatorObjectHandle $handle,
|
||||||
|
array $options) {
|
||||||
|
|
||||||
|
$is_video = ($tag == 'video');
|
||||||
|
|
||||||
if (idx($options, 'autoplay')) {
|
if (idx($options, 'autoplay')) {
|
||||||
$preload = 'auto';
|
$preload = 'auto';
|
||||||
$autoplay = 'autoplay';
|
$autoplay = 'autoplay';
|
||||||
|
} else {
|
||||||
|
// If we don't preload video, the user can't see the first frame and
|
||||||
|
// has no clue what they're looking at, so always preload.
|
||||||
|
if ($is_video) {
|
||||||
|
$preload = 'auto';
|
||||||
} else {
|
} else {
|
||||||
$preload = 'none';
|
$preload = 'none';
|
||||||
|
}
|
||||||
$autoplay = null;
|
$autoplay = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->newTag(
|
return $this->newTag(
|
||||||
'audio',
|
$tag,
|
||||||
array(
|
array(
|
||||||
'controls' => 'controls',
|
'controls' => 'controls',
|
||||||
'preload' => $preload,
|
'preload' => $preload,
|
||||||
'autoplay' => $autoplay,
|
'autoplay' => $autoplay,
|
||||||
'loop' => idx($options, 'loop') ? 'loop' : null,
|
'loop' => idx($options, 'loop') ? 'loop' : null,
|
||||||
|
'alt' => $options['alt'],
|
||||||
|
'class' => 'phabricator-media',
|
||||||
),
|
),
|
||||||
$this->newTag(
|
$this->newTag(
|
||||||
'source',
|
'source',
|
||||||
|
|
|
@ -802,6 +802,16 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
||||||
return idx($mime_map, $mime_type);
|
return idx($mime_map, $mime_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isVideo() {
|
||||||
|
if (!$this->isViewableInBrowser()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mime_map = PhabricatorEnv::getEnvConfig('files.video-mime-types');
|
||||||
|
$mime_type = $this->getMimeType();
|
||||||
|
return idx($mime_map, $mime_type);
|
||||||
|
}
|
||||||
|
|
||||||
public function isTransformableImage() {
|
public function isTransformableImage() {
|
||||||
// NOTE: The way the 'gd' extension works in PHP is that you can install it
|
// NOTE: The way the 'gd' extension works in PHP is that you can install it
|
||||||
// with support for only some file types, so it might be able to handle
|
// with support for only some file types, so it might be able to handle
|
||||||
|
|
|
@ -410,18 +410,29 @@ You can set file display options like this:
|
||||||
|
|
||||||
{F123, layout=left, float, size=full, alt="a duckling"}
|
{F123, layout=left, float, size=full, alt="a duckling"}
|
||||||
|
|
||||||
Valid options are:
|
Valid options for all files are:
|
||||||
|
|
||||||
- **layout** left (default), center, right, inline, link (render a link
|
- **layout** left (default), center, right, inline, link (render a link
|
||||||
instead of a thumbnail for images)
|
instead of a thumbnail for images)
|
||||||
|
- **name** with `layout=link` or for non-images, use this name for the link
|
||||||
|
text
|
||||||
|
- **alt** Provide alternate text for assistive technologies.
|
||||||
|
|
||||||
|
Image files support these options:
|
||||||
|
|
||||||
- **float** If layout is set to left or right, the image will be floated so
|
- **float** If layout is set to left or right, the image will be floated so
|
||||||
text wraps around it.
|
text wraps around it.
|
||||||
- **size** thumb (default), full
|
- **size** thumb (default), full
|
||||||
- **name** with `layout=link` or for non-images, use this name for the link
|
|
||||||
text
|
|
||||||
- **width** Scale image to a specific width.
|
- **width** Scale image to a specific width.
|
||||||
- **height** Scale image to a specific height.
|
- **height** Scale image to a specific height.
|
||||||
- **alt** Provide alternate text for assistive technologies.
|
|
||||||
|
Audio and video files support these options:
|
||||||
|
|
||||||
|
- **media**: Specify the media type as `audio` or `video`. This allows you
|
||||||
|
to disambiguate how file format which may contain either audio or video
|
||||||
|
should be rendered.
|
||||||
|
- **loop**: Loop this media.
|
||||||
|
- **autoplay**: Automatically begin playing this media.
|
||||||
|
|
||||||
== Embedding Countdowns
|
== Embedding Countdowns
|
||||||
|
|
||||||
|
|
|
@ -218,6 +218,17 @@
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video.phabricator-media {
|
||||||
|
background: {$greybackground};
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-remarkup video {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
min-width: 240px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
.phabricator-remarkup-mention-exists {
|
.phabricator-remarkup-mention-exists {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background: #e6f3ff;
|
background: #e6f3ff;
|
||||||
|
|
|
@ -160,6 +160,13 @@
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.phui-property-list-video {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 90%;
|
||||||
|
min-width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
/* When tags appear in property lists, give them a little more vertical
|
/* When tags appear in property lists, give them a little more vertical
|
||||||
spacing. */
|
spacing. */
|
||||||
.phui-property-list-view .phui-tag-view {
|
.phui-property-list-view .phui-tag-view {
|
||||||
|
|
Loading…
Add table
Reference in a new issue