From f7adfc2869d68bac7bc73c52796ce4fe0f53958d Mon Sep 17 00:00:00 2001 From: Aaron Boodman Date: Sun, 18 May 2014 12:16:35 -0700 Subject: [PATCH] Support video content in detail view Change-Id: I1a8d7bceefeff94a70dedf7cee7a9778fa61dd43 --- .../camlistored/ui/blob_item_video_content.js | 25 ++++++++++++++- server/camlistored/ui/image_detail.js | 31 +++++++++++++------ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/server/camlistored/ui/blob_item_video_content.js b/server/camlistored/ui/blob_item_video_content.js index 1c8941cae..e338f170f 100644 --- a/server/camlistored/ui/blob_item_video_content.js +++ b/server/camlistored/ui/blob_item_video_content.js @@ -125,6 +125,29 @@ cam.BlobItemVideoContent = React.createClass({ }, }); +cam.BlobItemVideoContent.isVideo = function(rm) { + // From http://en.wikipedia.org/wiki/List_of_file_formats + // TODO(aa): Fix this quick hack once the server indexes movies and gives us more information. + var extensions = [ + '3gp', + 'aav', + 'asf', + 'avi', + 'dat', + 'm1v', + 'm2v', + 'm4v', + 'mov', + 'mp4', + 'mpe', + 'mpeg', + 'mpg', + 'ogg', + 'wmv', + ]; + return rm && rm.file && goog.array.some(extensions, goog.string.endsWith.bind(null, rm.file.fileName.toLowerCase())); +}; + cam.BlobItemVideoContent.getHandler = function(blobref, searchSession, href) { var rm = searchSession.getResolvedMeta(blobref); @@ -147,7 +170,7 @@ cam.BlobItemVideoContent.getHandler = function(blobref, searchSession, href) { 'ogg', 'wmv', ]; - if (rm && rm.file && goog.array.some(extensions, goog.string.endsWith.bind(null, rm.file.fileName.toLowerCase()))) { + if (cam.BlobItemVideoContent.isVideo(rm)) { return new cam.BlobItemVideoContent.Handler(rm, href) } diff --git a/server/camlistored/ui/image_detail.js b/server/camlistored/ui/image_detail.js index 7bf2da67d..5d01943b0 100644 --- a/server/camlistored/ui/image_detail.js +++ b/server/camlistored/ui/image_detail.js @@ -16,6 +16,7 @@ limitations under the License. goog.provide('cam.ImageDetail'); +goog.require('cam.BlobItemVideoContent'); goog.require('cam.PropertySheetContainer'); goog.require('cam.Thumber'); @@ -35,9 +36,17 @@ cam.ImageDetail = React.createClass({ width: React.PropTypes.number.isRequired, }, + isVideo_: function() { + return !this.isImage_(); + }, + + isImage_: function() { + return Boolean(this.props.resolvedMeta.image); + }, + componentWillReceiveProps: function(nextProps) { if (this.props == nextProps || this.props.resolvedMeta.blobRef != nextProps.resolvedMeta.blobRef) { - this.thumber_ = cam.Thumber.fromImageMeta(nextProps.resolvedMeta); + this.thumber_ = nextProps.resolvedMeta.image && cam.Thumber.fromImageMeta(nextProps.resolvedMeta); this.setState({imgHasLoaded: false}); } }, @@ -124,16 +133,18 @@ cam.ImageDetail = React.createClass({ getImg_: function() { var transition = React.addons.TransitionGroup({transitionName: 'detail-img'}, []); if (this.imgSize_) { + var ctor = this.props.resolvedMeta.image ? React.DOM.img : React.DOM.video; transition.props.children.push( - React.DOM.img({ + ctor({ className: React.addons.classSet({ 'detail-view-img': true, - 'detail-view-img-loaded': this.state.imgHasLoaded + 'detail-view-img-loaded': this.isImage_() ? this.state.imgHasLoaded : true, }), + controls: true, // We want each image to have its own node in the DOM so that during the crossfade, we don't see the image jump to the next image's size. key: 'img' + this.props.resolvedMeta.blobRef, - onLoad: this.onImgLoad_, - src: this.thumber_.getSrc(this.imgSize_.height), + onLoad: this.isImage_() ? this.onImgLoad_ : null, + src: this.isImage_() ? this.thumber_.getSrc(this.imgSize_.height) : './download/' + this.props.resolvedMeta.blobRef + '/' + this.props.resolvedMeta.file.fileName, style: this.getCenteredProps_(this.imgSize_.width, this.imgSize_.height) }) ); @@ -143,7 +154,7 @@ cam.ImageDetail = React.createClass({ getPiggy_: function() { var transition = React.addons.TransitionGroup({transitionName: 'detail-piggy'}, []); - if (!this.state.imgHasLoaded) { + if (this.isImage_() && !this.state.imgHasLoaded) { transition.props.children.push( cam.SpritedAnimation({ src: 'glitch/npc_piggy__x1_walk_png_1354829432.png', @@ -173,8 +184,8 @@ cam.ImageDetail = React.createClass({ }, getImgSize_: function() { - if (!this.props.resolvedMeta.image) { - return null; + if (this.isVideo_()) { + return new goog.math.Size(this.props.width, this.props.height); } var rawSize = new goog.math.Size(this.props.resolvedMeta.image.width, this.props.resolvedMeta.image.height); var available = new goog.math.Size( @@ -212,7 +223,7 @@ cam.ImageDetail.getAspect = function(blobref, searchSession) { pm = null; } - return rm && rm.image ? new cam.ImageDetail.Aspect(rm, pm) : null; + return rm && (rm.image || cam.BlobItemVideoContent.isVideo(rm)) ? new cam.ImageDetail.Aspect(rm, pm) : null; // We don't handle camliContentImage like BlobItemImage.getHandler does because that only tells us what image to display in the search results. It doesn't actually make the permanode an image or anything. }; @@ -223,7 +234,7 @@ cam.ImageDetail.Aspect = function(resolvedMeta, permanodeMeta) { }; cam.ImageDetail.Aspect.prototype.getTitle = function() { - return 'Image'; + return this.resolvedMeta_.image ? 'Image' : 'Video'; }; // TODO(aa): Piggy should move into cam.Detail and use an onload handler to turn on/off.