001package gwt.material.design.addins.client.camera; 002 003/* 004 * #%L 005 * GwtMaterial 006 * %% 007 * Copyright (C) 2016 GwtMaterialDesign 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import com.google.gwt.canvas.client.Canvas; 024import com.google.gwt.dom.client.CanvasElement; 025import com.google.gwt.dom.client.Document; 026import com.google.gwt.dom.client.Element; 027import com.google.gwt.dom.client.VideoElement; 028import com.google.gwt.event.shared.HandlerRegistration; 029import gwt.material.design.addins.client.camera.base.HasCameraCaptureHandlers; 030import gwt.material.design.addins.client.camera.events.CameraCaptureEvent; 031import gwt.material.design.addins.client.camera.events.CameraCaptureEvent.CaptureStatus; 032import gwt.material.design.addins.client.camera.events.CameraCaptureHandler; 033import gwt.material.design.client.base.MaterialWidget; 034 035//@formatter:off 036/** 037 * <p> 038 * MaterialCameraCapture is a widget that captures the video stream from the camera, using the 039 * HTML5 {@code MediaDevices.getUserMedia()} (Streams API). This widget can capture images from the video, to allow 040 * the upload to the server of photos from the camera. 041 * </p> 042 * 043 * <h3>XML Namespace Declaration</h3> 044 * <pre> 045 * {@code 046 * xmlns:ma='urn:import:gwt.material.design.addins.client' 047 * } 048 * </pre> 049 * 050 * <h3>UiBinder Usage:</h3> 051 * <p><pre> 052 * {@code 053 * <ma:camera.MaterialCameraCapture ui:field="camera" /> 054 * } 055 * </pre></p> 056 * 057 * <h3>Java Usage:</h3> 058 * <p><pre> 059 * if (MaterialCameraCapture.isSupported()){ 060 * MaterialCameraCapture camera = new MaterialCameraCapture(); 061 * camera.addCameraCaptureHandler(new CameraCaptureHandler() { 062 * {@literal @}Override 063 * public void onCameraCaptureChange(CameraCaptureEvent event) { 064 * if (event.getCaptureStatus() == CaptureStatus.ERRORED){ 065 * Window.alert("Error on starting the camera capture: " + event.getErrorMessage()); 066 * ((MaterialCameraCapture)event.getSource()).removeFromParent(); 067 * } 068 * } 069 * }); 070 * add(camera); //adds to the layout 071 * } 072 * else { 073 * Window.alert("Sorry, your browser doesn't support the camera capture."); 074 * } 075 * </pre></p> 076 * 077 * <h3>Styling:</h3> 078 * <p> 079 * To limit the width of the camera capture widget on mobile devices, you can use {@code max-width: 100%} on the widget. 080 * The browser will take care of the aspect ratio of the video. 081 * </p> 082 * 083 * <h3>Notice:</h3> 084 * <p> 085 * This widget only works on pages served by a secure protocol (HTTPS). For the browser compatibility, 086 * access <a href="http://caniuse.com/#feat=stream">http://caniuse.com/#feat=stream</a> 087 * </p> 088 * 089 * @author gilberto-torrezan 090 */ 091// @formatter:on 092public class MaterialCameraCapture extends MaterialWidget implements HasCameraCaptureHandlers { 093 094 protected boolean pauseOnUnload = true; 095 096 public MaterialCameraCapture() { 097 super(Document.get().createVideoElement()); 098 } 099 100 /** 101 * Captures the current frame of the video to an image data URL. It's the same as calling 102 * {@link #captureToDataURL(String)} using "image/png". 103 * 104 * @return The Data URL of the captured image, which can be used as "src" on an "img" element 105 * or sent to the server 106 */ 107 public String captureToDataURL() { 108 return captureToDataURL("image/png"); 109 } 110 111 /** 112 * Captures the current frame of the video to an image data URL. 113 * 114 * @param mimeType The type of the output image, such as "image/png" or "image/jpeg". 115 * 116 * @return The Data URL of the captured image, which can be used as "src" on an "img" element 117 * or sent to the server 118 */ 119 public String captureToDataURL(String mimeType) { 120 return nativeCaptureToDataURL(Canvas.createIfSupported().getCanvasElement(), getElement(), mimeType); 121 } 122 123 @Override 124 protected void onLoad() { 125 super.onLoad(); 126 VideoElement el = getElement().cast(); 127 if (el.getSrc() == null || el.isPaused()) { 128 play(); 129 } 130 } 131 132 @Override 133 protected void onUnload() { 134 if (pauseOnUnload) { 135 pause(); 136 } 137 } 138 139 /** 140 * <p> 141 * Starts the video stream from the camera. This is called when the component is loaded. 142 * Use {@link CameraCaptureHandler}s to be notified when the stream actually starts or if 143 * an error occurs. 144 * </p> 145 * <p> 146 * At this point the user is requested by the browser to allow the application to use the camera. 147 * If the user doesn't allow it, an error is notified to the {@link CameraCaptureHandler}s. 148 * </p> 149 */ 150 public void play() { 151 if (!isSupported()) { 152 onCameraCaptureError("MaterialCameraCapture is not supported in this browser."); 153 return; 154 } 155 VideoElement el = getElement().cast(); 156 if (el.getSrc() == null) { 157 nativePlay(getElement()); 158 } else { 159 el.play(); 160 } 161 } 162 163 /** 164 * Restarts the video stream from the camera. 165 * The user is requested again by the browser to allow the application to use the camera. 166 */ 167 public void restart() { 168 if (!isSupported()) { 169 onCameraCaptureError("MaterialCameraCapture is not supported in this browser."); 170 return; 171 } 172 nativePlay(getElement()); 173 } 174 175 /** 176 * Pauses the video stream from the camera. 177 */ 178 public void pause() { 179 VideoElement el = getElement().cast(); 180 el.pause(); 181 onCameraCapturePause(); 182 } 183 184 /** 185 * Sets if the camera capture should pause when the widget is unloaded. 186 * The default is <code>true</code>. 187 */ 188 public void setPauseOnUnload(boolean pauseOnUnload) { 189 this.pauseOnUnload = pauseOnUnload; 190 } 191 192 /** 193 * Returns if the camera capture should pause when the widget is unloaded. 194 * The default is <code>true</code>. 195 */ 196 public boolean isPauseOnUnload() { 197 return pauseOnUnload; 198 } 199 200 /** 201 * Native call to the streams API 202 */ 203 protected native void nativePlay(Element video)/*-{ 204 var navigator = $wnd.navigator; 205 var widget = this; 206 207 var onSuccess = function(stream) { 208 var vendorURL = $wnd.URL || $wnd.webkitURL; 209 video.src = vendorURL.createObjectURL(stream); 210 video.play(); 211 widget.@gwt.material.design.addins.client.camera.MaterialCameraCapture::onCameraCaptureLoad()(); 212 }; 213 214 var onFailure = function(err) { 215 console.log("MaterialCameraCapture: An error occured! " + err); 216 widget.@gwt.material.design.addins.client.camera.MaterialCameraCapture::onCameraCaptureError(Ljava/lang/String;)(err.message); 217 }; 218 219 //using premisses 220 if (navigator.mediaDevices.getUserMedia){ 221 var p = navigator.mediaDevices.getUserMedia({video: true, audio: false}); 222 p.then(onSuccess); 223 //workaround for the catch keyword. See https://groups.google.com/forum/#!topic/google-web-toolkit/t1KGh-7KL-k 224 p["catch"](onFailure); 225 } 226 else { 227 navigator.getMedia = (navigator.getUserMedia || 228 navigator.webkitGetUserMedia || 229 navigator.mozGetUserMedia || 230 navigator.msGetUserMedia); 231 navigator.getMedia({video: true, audio: false}, onSuccess, onFailure); 232 } 233 }-*/; 234 235 /** 236 * Native call to capture the frame of the video stream. 237 */ 238 protected native String nativeCaptureToDataURL(CanvasElement canvas, Element video, String mimeType)/*-{ 239 var width = video.videoWidth; 240 var height = video.videoHeight; 241 242 if (isNaN(width) || isNaN(height)) { 243 width = video.clientWidth; 244 height = video.clientHeight; 245 } 246 247 canvas.width = width; 248 canvas.height = height; 249 250 var context = canvas.getContext('2d'); 251 context.drawImage(video, 0, 0, width, height); 252 253 var data = canvas.toDataURL(mimeType); 254 return data; 255 }-*/; 256 257 /** 258 * Tests if the browser supports the Streams API. This should be called before creating any 259 * MaterialCameraCapture widgets to avoid errors on the browser. 260 * 261 * @return <code>true</code> if the browser supports this widget, <code>false</code> otherwise 262 */ 263 public static native boolean isSupported()/*-{ 264 return !!(navigator.mediaDevices.getUserMedia || 265 navigator.getUserMedia || 266 navigator.webkitGetUserMedia || 267 navigator.mozGetUserMedia || 268 navigator.msGetUserMedia); 269 }-*/; 270 271 /** 272 * Called by the component when the stream has started. 273 */ 274 protected void onCameraCaptureLoad() { 275 CameraCaptureEvent.fire(this, CaptureStatus.STARTED); 276 } 277 278 /** 279 * Called by the component when the stream has paused. 280 */ 281 protected void onCameraCapturePause() { 282 CameraCaptureEvent.fire(this, CaptureStatus.PAUSED); 283 } 284 285 /** 286 * Called by the component when the stream when an error occurs. 287 */ 288 protected void onCameraCaptureError(String error) { 289 CameraCaptureEvent.fire(this, error); 290 } 291 292 @Override 293 public HandlerRegistration addCameraCaptureHandler(final CameraCaptureHandler handler) { 294 return addHandler(new CameraCaptureHandler() { 295 @Override 296 public void onCameraCaptureChange(CameraCaptureEvent event) { 297 if(isEnabled()){ 298 handler.onCameraCaptureChange(event); 299 } 300 } 301 }, CameraCaptureEvent.getType()); 302 } 303 304}