show a preview for video files

Daniel Gultsch created

Change summary

art/play_video.svg                                                |  59 
art/render.rb                                                     |   1 
src/main/java/eu/siacs/conversations/persistance/FileBackend.java | 140 
src/main/res/drawable-hdpi/play_video.png                         |   0 
src/main/res/drawable-mdpi/play_video.png                         |   0 
src/main/res/drawable-xhdpi/play_video.png                        |   0 
src/main/res/drawable-xxhdpi/play_video.png                       |   0 
src/main/res/drawable-xxxhdpi/play_video.png                      |   0 
8 files changed, 176 insertions(+), 24 deletions(-)

Detailed changes

art/play_video.svg 🔗

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="48"
+   height="48"
+   viewBox="0 0 48 48"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="play_video.svg">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1916"
+     inkscape:window-height="1156"
+     id="namedview8"
+     showgrid="false"
+     inkscape:zoom="4.9166667"
+     inkscape:cx="0.91525424"
+     inkscape:cy="24"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2" />
+  <path
+     d="M0 0h48v48H0z"
+     fill="none"
+     id="path4" />
+  <path
+     d="M20 33l12-9-12-9v18zm4-29C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16z"
+     id="path6"
+     style="fill:#ffffff;fill-opacity:0.7019608;opacity:1;stroke:none;stroke-opacity:0.38039216" />
+</svg>

art/render.rb 🔗

@@ -13,6 +13,7 @@ resolutions = {
 images = {
 	'ic_launcher.svg' => ['ic_launcher', 48],
 	'main_logo.svg' => ['main_logo', 200],
+	'play_video.svg' => ['play_video', 96],
 	'conversations_mono.svg' => ['ic_notification', 24],
 	'ic_received_indicator.svg' => ['ic_received_indicator', 12],
 	'ic_send_text_offline.svg' => ['ic_send_text_offline', 36],

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -10,6 +10,7 @@ import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.RectF;
+import android.media.MediaMetadataRetriever;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Environment;
@@ -380,21 +381,43 @@ public class FileBackend {
 				if (thumbnail != null) {
 					return thumbnail;
 				}
-				File file = getFile(message);
-				BitmapFactory.Options options = new BitmapFactory.Options();
-				options.inSampleSize = calcSampleSize(file, size);
-				Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
-				if (fullsize == null) {
-					throw new FileNotFoundException();
+				DownloadableFile file = getFile(message);
+				if (file.getMimeType().startsWith("video/")) {
+					thumbnail = getVideoPreview(file, size);
+				} else {
+					Bitmap fullsize = getFullsizeImagePreview(file, size);
+					if (fullsize == null) {
+						throw new FileNotFoundException();
+					}
+					thumbnail = resize(fullsize, size);
+					thumbnail = rotate(thumbnail, getRotation(file));
 				}
-				thumbnail = resize(fullsize, size);
-				thumbnail = rotate(thumbnail, getRotation(file));
 				this.mXmppConnectionService.getBitmapCache().put(uuid, thumbnail);
 			}
 		}
 		return thumbnail;
 	}
 
+	private Bitmap getFullsizeImagePreview(File file, int size) {
+		BitmapFactory.Options options = new BitmapFactory.Options();
+		options.inSampleSize = calcSampleSize(file, size);
+		return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+	}
+
+	private Bitmap getVideoPreview(File file, int size) {
+		MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
+		metadataRetriever.setDataSource(file.getAbsolutePath());
+		Bitmap frame = metadataRetriever.getFrameAtTime(0);
+		metadataRetriever.release();
+		frame = resize(frame, size);
+		Canvas canvas = new Canvas(frame);
+		Bitmap play = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), R.drawable.play_video);
+		float x = (frame.getWidth() - play.getWidth()) / 2.0f;
+		float y = (frame.getHeight() - play.getHeight()) / 2.0f;
+		canvas.drawBitmap(play,x,y,null);
+		return frame;
+	}
+
 	public Uri getTakePhotoUri() {
 		StringBuilder pathBuilder = new StringBuilder();
 		pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
@@ -656,26 +679,95 @@ public class FileBackend {
 
 	public void updateFileParams(Message message, URL url) {
 		DownloadableFile file = getFile(message);
-		if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) {
-			BitmapFactory.Options options = new BitmapFactory.Options();
-			options.inJustDecodeBounds = true;
-			BitmapFactory.decodeFile(file.getAbsolutePath(), options);
-			int rotation = getRotation(file);
-			boolean rotated = rotation == 90 || rotation == 270;
-			int imageHeight = rotated ? options.outWidth : options.outHeight;
-			int imageWidth = rotated ? options.outHeight : options.outWidth;
-			if (url == null) {
-				message.setBody(Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
-			} else {
-				message.setBody(url.toString()+"|"+Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
+		boolean image = message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/");
+		boolean video = message.getMimeType().startsWith("video/");
+		if (image || video) {
+			try {
+				Dimensions dimensions = image ? getImageDimensions(file) : getVideoDimensions(file);
+				if (url == null) {
+					message.setBody(Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height);
+				} else {
+					message.setBody(url.toString() + "|" + Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height);
+				}
+				return;
+			} catch (NotAVideoFile notAVideoFile) {
+				Log.d(Config.LOGTAG,"file with mime type "+file.getMimeType()+" was not a video file");
+				//fall threw
 			}
+		}
+		if (url != null) {
+			message.setBody(url.toString()+"|"+Long.toString(file.getSize()));
 		} else {
-			if (url != null) {
-				message.setBody(url.toString()+"|"+Long.toString(file.getSize()));
-			} else {
-				message.setBody(Long.toString(file.getSize()));
+			message.setBody(Long.toString(file.getSize()));
+		}
+
+	}
+
+	private Dimensions getImageDimensions(File file) {
+		BitmapFactory.Options options = new BitmapFactory.Options();
+		options.inJustDecodeBounds = true;
+		BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+		int rotation = getRotation(file);
+		boolean rotated = rotation == 90 || rotation == 270;
+		int imageHeight = rotated ? options.outWidth : options.outHeight;
+		int imageWidth = rotated ? options.outHeight : options.outWidth;
+		return new Dimensions(imageHeight, imageWidth);
+	}
+
+	private Dimensions getVideoDimensions(File file) throws NotAVideoFile {
+		MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
+		metadataRetriever.setDataSource(file.getAbsolutePath());
+		String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
+		if (hasVideo == null) {
+			throw new NotAVideoFile();
+		}
+		int rotation = extractRotationFromMediaRetriever(metadataRetriever);
+		boolean rotated = rotation == 90 || rotation == 270;
+		int height;
+		try {
+			String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+			height = Integer.parseInt(h);
+		} catch (Exception e) {
+			height = -1;
+		}
+		int width;
+		try {
+			String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+			width = Integer.parseInt(w);
+		} catch (Exception e) {
+			width = -1;
+		}
+		metadataRetriever.release();
+		Log.d(Config.LOGTAG,"extracted video dims "+width+"x"+height);
+		return rotated ? new Dimensions(width, height) : new Dimensions(height, width);
+	}
+
+	private int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
+		int rotation;
+		if (Build.VERSION.SDK_INT >= 17) {
+			String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+			try {
+				rotation = Integer.parseInt(r);
+			} catch (Exception e) {
+				rotation = 0;
 			}
+		} else {
+			rotation = 0;
 		}
+		return rotation;
+	}
+
+	private class Dimensions {
+		public final int width;
+		public final int height;
+
+		public Dimensions(int height, int width) {
+			this.width = width;
+			this.height = height;
+		}
+	}
+
+	private class NotAVideoFile extends Exception {
 
 	}