Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(180)

Side by Side Diff: chrome/browser/resources/chromeos/login/oobe_screen_user_image.js

Issue 10532048: [cros] Initial WebRTC-enabled implementation of user image picker on OOBE. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview Oobe user image screen implementation. 6 * @fileoverview Oobe user image screen implementation.
7 */ 7 */
8 8
9 cr.define('oobe', function() { 9 cr.define('oobe', function() {
10
11 var UserImagesGrid = options.UserImagesGrid; 10 var UserImagesGrid = options.UserImagesGrid;
12 var ButtonImages = UserImagesGrid.ButtonImages; 11 var ButtonImages = UserImagesGrid.ButtonImages;
13 12
14 /** 13 /**
15 * Array of button URLs used on this page. 14 * Array of button URLs used on this page.
16 * @type {Array.<string>} 15 * @type {Array.<string>}
16 * @const
17 */ 17 */
18 const ButtonImageUrls = [ 18 var ButtonImageUrls = [
19 ButtonImages.TAKE_PHOTO 19 ButtonImages.TAKE_PHOTO
20 ]; 20 ];
21 21
22 /** 22 /**
23 * Creates a new oobe screen div. 23 * Creates a new OOBE screen div.
24 * @constructor 24 * @constructor
25 * @extends {HTMLDivElement} 25 * @extends {HTMLDivElement}
26 */ 26 */
27 var UserImageScreen = cr.ui.define('div'); 27 var UserImageScreen = cr.ui.define('div');
28 28
29 /** 29 /**
30 * Registers with Oobe. 30 * Registers with Oobe.
31 */ 31 */
32 UserImageScreen.register = function() { 32 UserImageScreen.register = function() {
33 var screen = $('user-image'); 33 var screen = $('user-image');
34 var isWebRTC = document.documentElement.getAttribute('camera') == 'webrtc';
35 console.log('WebRTC: ' + isWebRTC);
Nikita (slow) 2012/06/07 15:26:55 Remove console output.
Ivan Korotkov 2012/06/09 14:19:01 Done.
36 UserImageScreen.prototype = isWebRTC ? UserImageScreenWebRTCProto :
37 UserImageScreenOldProto;
34 UserImageScreen.decorate(screen); 38 UserImageScreen.decorate(screen);
35 Oobe.getInstance().registerScreen(screen); 39 Oobe.getInstance().registerScreen(screen);
36 }; 40 };
37 41
38 UserImageScreen.prototype = { 42 var UserImageScreenOldProto = {
39 __proto__: HTMLDivElement.prototype, 43 __proto__: HTMLDivElement.prototype,
40 44
41 /** 45 /**
42 * Currently selected user image index (take photo button is with zero 46 * Currently selected user image index (take photo button is with zero
43 * index). 47 * index).
44 * @type {number} 48 * @type {number}
45 */ 49 */
46 selectedUserImage_: -1, 50 selectedUserImage_: -1,
47 51
48 /** @inheritDoc */ 52 /** @inheritDoc */
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
95 99
96 /** 100 /**
97 * Buttons in oobe wizard's button strip. 101 * Buttons in oobe wizard's button strip.
98 * @type {array} Array of Buttons. 102 * @type {array} Array of Buttons.
99 */ 103 */
100 get buttons() { 104 get buttons() {
101 var okButton = this.ownerDocument.createElement('button'); 105 var okButton = this.ownerDocument.createElement('button');
102 okButton.id = 'ok-button'; 106 okButton.id = 'ok-button';
103 okButton.textContent = localStrings.getString('okButtonText'); 107 okButton.textContent = localStrings.getString('okButtonText');
104 okButton.addEventListener('click', this.acceptImage_.bind(this)); 108 okButton.addEventListener('click', this.acceptImage_.bind(this));
105 return [ okButton ]; 109 return [okButton];
106 }, 110 },
107 111
108 /** 112 /**
109 * The caption to use for the Profile image preview. 113 * The caption to use for the Profile image preview.
110 * @type {string} 114 * @type {string}
111 */ 115 */
112 get profileImageCaption() { 116 get profileImageCaption() {
113 return this.profileImageCaption_; 117 return this.profileImageCaption_;
114 }, 118 },
115 set profileImageCaption(value) { 119 set profileImageCaption(value) {
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after
315 * Updates the image preview caption. 319 * Updates the image preview caption.
316 * @private 320 * @private
317 */ 321 */
318 updateCaption_: function() { 322 updateCaption_: function() {
319 $('user-image-preview-caption').textContent = 323 $('user-image-preview-caption').textContent =
320 this.profileImageSelected ? this.profileImageCaption : ''; 324 this.profileImageSelected ? this.profileImageCaption : '';
321 }, 325 },
322 326
323 /** 327 /**
324 * Updates localized content of the screen that is not updated via template. 328 * Updates localized content of the screen that is not updated via template.
325 * @public
326 */ 329 */
327 updateLocalizedContent: function() { 330 updateLocalizedContent: function() {
328 this.updateProfileImageCaption_(); 331 this.updateProfileImageCaption_();
332 },
333
334 /**
335 * Updates profile image caption.
336 * @private
337 */
338 updateProfileImageCaption_: function() {
339 this.profileImageCaption = localStrings.getString(
340 this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto');
341 }
342 };
343
344 var UserImageScreenWebRTCProto = {
345 __proto__: HTMLDivElement.prototype,
346
347 /**
348 * Currently selected user image index (take photo button is with zero
349 * index).
350 * @type {number}
351 */
352 selectedUserImage_: -1,
353
354 /** @inheritDoc */
355 decorate: function(element) {
356 var imageGrid = $('user-image-grid');
357 UserImagesGrid.decorate(imageGrid);
358
359 imageGrid.addEventListener('change',
360 this.handleSelection_.bind(this));
361 imageGrid.addEventListener('activate',
362 this.handleImageActivated_.bind(this));
363 imageGrid.addEventListener('dblclick',
364 this.handleImageDblClick_.bind(this));
365
366 // Profile image data (if present).
367 this.profileImage_ = imageGrid.addItem(
368 ButtonImages.PROFILE_PICTURE,
369 undefined, undefined, undefined,
370 function(el) { // Custom decorator for Profile image element.
371 var spinner = el.ownerDocument.createElement('div');
372 spinner.className = 'spinner';
373 var spinnerBg = el.ownerDocument.createElement('div');
374 spinnerBg.className = 'spinner-bg';
375 spinnerBg.appendChild(spinner);
376 el.appendChild(spinnerBg);
377 el.id = 'profile-image';
378 });
379 this.profileImage_.type = 'profile';
380 this.selectionType = 'default';
381
382 var video = $('user-image-stream');
383 video.addEventListener('canplay', this.handleVideoStarted_.bind(this));
384 video.addEventListener('timeupdate', this.handleVideoUpdate_.bind(this));
385 $('take-photo').addEventListener('click',
386 this.handleTakePhoto_.bind(this));
387 $('discard-photo').addEventListener('click',
388 this.handleDiscardPhoto_.bind(this));
389 this.cameraImage = null;
390 this.checkVideo_();
Nikita (slow) 2012/06/07 15:26:55 Not sure if this the right place for this check.
Ivan Korotkov 2012/06/09 14:19:01 Right, fixed.
391 },
392
393 /**
394 * Header text of the screen.
395 * @type {string}
396 */
397 get header() {
398 return localStrings.getString('userImageScreenTitle');
399 },
400
401 /**
402 * Buttons in oobe wizard's button strip.
403 * @type {array} Array of Buttons.
404 */
405 get buttons() {
406 var okButton = this.ownerDocument.createElement('button');
407 okButton.id = 'ok-button';
408 okButton.textContent = localStrings.getString('okButtonText');
409 okButton.addEventListener('click', this.acceptImage_.bind(this));
410 return [okButton];
411 },
412
413 /**
414 * The caption to use for the Profile image preview.
415 * @type {string}
416 */
417 get profileImageCaption() {
418 return this.profileImageCaption_;
419 },
420 set profileImageCaption(value) {
421 this.profileImageCaption_ = value;
422 this.updateCaption_();
423 },
424
425 /**
426 * True if the Profile image is being loaded.
427 * @type {boolean}
428 */
429 get profileImageLoading() {
430 return this.profileImageLoading_;
431 },
432 set profileImageLoading(value) {
433 this.profileImageLoading_ = value;
434 $('user-image-screen-main').classList[
435 value ? 'add' : 'remove']('profile-image-loading');
436 this.updateProfileImageCaption_();
437 },
438
439 /**
440 * True when camera is in live mode (i.e. no still photo selected).
441 * @type {boolean}
442 */
443 get cameraLive() {
444 return this.cameraLive_;
445 },
446 set cameraLive(value) {
447 this.cameraLive_ = value;
448 $('user-image-preview').classList[value ? 'add' : 'remove']('live');
449 },
450
451 /**
452 * Type of the selected image (one of 'default', 'profile', 'camera').
453 * @type {string}
454 */
455 get selectionType() {
456 return this.selectionType_;
457 },
458 set selectionType(value) {
459 this.selectionType_ = value;
460 var previewClassList = $('user-image-preview').classList;
461 previewClassList[value == 'default' ? 'add' : 'remove']('default-image');
462 previewClassList[value == 'profile' ? 'add' : 'remove']('profile-image');
463 previewClassList[value == 'camera' ? 'add' : 'remove']('camera');
464 this.updateCaption_();
465 },
466
467 /**
468 * Handles image activation (by pressing Enter).
469 * @private
470 */
471 handleImageActivated_: function() {
472 switch ($('user-image-grid').selectedItemUrl) {
473 case ButtonImages.TAKE_PHOTO:
474 this.handleTakePhoto_();
475 break;
476 default:
477 this.acceptImage_();
478 break;
479 }
480 },
481
482 /**
483 * Handles photo capture from the live camera stream.
484 * @private
485 */
486 handleTakePhoto_: function() {
487 var self = this;
488 var photoURL = this.captureFrame_($('user-image-stream'),
489 {width: 320, height: 320});
Nikita (slow) 2012/06/07 15:26:55 nit: Extract constants.
Ivan Korotkov 2012/06/09 14:19:01 Done.
490 chrome.send('photoTaken', [photoURL]);
491 // Wait until image is loaded before displaying it.
492 var previewImg = new Image();
493 previewImg.addEventListener('load', function(e) {
494 self.cameraImage = this.src;
495 });
496 previewImg.src = photoURL;
497 },
498
499 /**
500 * Discard current photo and return to the live camera stream.
501 * @private
502 */
503 handleDiscardPhoto_: function() {
504 this.cameraImage = null;
505 },
506
507 /**
508 * Capture a single still frame from a <video> element.
509 * @param {HTMLVideoElement} video Video element to capture from.
510 * @param {{width: number, height: number}} destSize Capture size.
511 * @return {string} Captured frame as a data URL.
512 * @private
513 */
514 captureFrame_: function(video, destSize) {
515 var canvas = document.createElement('canvas');
516 canvas.width = destSize.width;
517 canvas.height = destSize.height;
518 var ctx = canvas.getContext('2d');
519 var W = video.videoWidth, H = video.videoHeight;
Nikita (slow) 2012/06/07 15:26:55 Split into 2 lines.
Nikita (slow) 2012/06/07 15:26:55 rename to width/height in lowercase
Ivan Korotkov 2012/06/09 14:19:01 Done.
Ivan Korotkov 2012/06/09 14:19:01 Done.
520 if (W < destSize.width || H < destSize.height)
521 console.error('Video capture size too small: ' + W + 'x' + H + '!');
522 var src = {};
523 if (W / destSize.width > H / destSize.height) {
524 // Full height, crop left/right.
525 src.height = H;
526 src.width = H * destSize.width / destSize.height;
527 } else {
528 // Full width, crop top/bottom.
529 src.width = W;
530 src.height = W * destSize.height / destSize.width;
531 }
532 src.x = (W - src.width) / 2;
533 src.y = (H - src.height) / 2;
534 ctx.drawImage(video, src.x, src.y, src.width, src.height,
535 0, 0, destSize.width, destSize.height);
536 return canvas.toDataURL('image/png');
537 },
538
539 /**
540 * Handles selection change.
541 * @private
542 */
543 handleSelection_: function() {
544 var selectedItem = $('user-image-grid').selectedItem;
545 if (selectedItem === null)
546 return;
547
548 // Update preview image URL.
549 var url = selectedItem.url;
550 $('user-image-preview-img').src = url;
551
552 // Update current selection type.
553 this.selectionType = selectedItem.type;
554
555 // Show grey silhouette with the same border as stock images.
556 if (/^chrome:\/\/theme\//.test(url))
557 $('user-image-preview').classList.add('default-image');
558
559 if (ButtonImageUrls.indexOf(url) == -1) {
560 // Non-button image is selected.
561 $('ok-button').disabled = false;
562 chrome.send('selectImage', [url]);
563 } else {
564 $('ok-button').disabled = true;
565 }
566 },
567
568 /**
569 * Handles double click on the image grid.
570 * @param {Event} e Double click Event.
571 */
572 handleImageDblClick_: function(e) {
573 // If an image is double-clicked and not the grid itself, handle this
574 // as 'OK' button button press.
575 if (e.target.id != 'user-image-grid')
576 this.acceptImage_();
577 },
578
579 /**
580 * Event handler that is invoked just before the screen is shown.
581 * @param {object} data Screen init payload.
582 */
583 onBeforeShow: function(data) {
584 Oobe.getInstance().headerHidden = true;
585 $('user-image-grid').updateAndFocus();
586 chrome.send('onUserImageScreenShown');
587 //this.checkVideo_();
Nikita (slow) 2012/06/07 15:26:55 Remove or add a second part of the initialization
Ivan Korotkov 2012/06/09 14:19:01 Done.
588 },
589
590 /**
591 * Accepts currently selected image, if possible.
592 * @private
593 */
594 acceptImage_: function() {
595 var okButton = $('ok-button');
596 if (!okButton.disabled) {
597 // This ensures that #ok-button won't be re-enabled again.
598 $('user-image-grid').disabled = true;
599 okButton.disabled = true;
600 chrome.send('onUserImageAccepted');
601 }
602 },
603
604 /**
605 * @param {boolean} present Whether a camera is present or not.
606 */
607 get cameraPresent() {
608 return this.cameraPresent_;
609 },
610 set cameraPresent(value) {
611 this.cameraPresent_ = value;
612 if (this.cameraLive)
613 this.cameraImage = null;
614 },
615
616 /**
617 * Start camera presence check.
618 * @private
619 */
620 checkVideo_: function() {
Nikita (slow) 2012/06/07 15:26:55 Rename to checkCameraPresence?
621 $('user-image-preview').classList.remove('online');
622 navigator.webkitGetUserMedia({video: true},
623 this.handleVideoAvailable_.bind(this),
624 this.handleVideoFailed_.bind(this));
625 },
626
627 /**
628 * Handles successful camera check.
629 * @param {MediaStream} stream Stream object as returned by getUserMedia.
630 * @private
631 */
632 handleVideoAvailable_: function(stream) {
633 $('user-image-stream').src = window.webkitURL.createObjectURL(stream);
634 this.cameraPresent = true;
635 },
636
637 /**
638 * Handles camera check failure.
639 * @param {NavigatorUserMediaError} err Error object.
640 * @private
641 */
642 handleVideoFailed_: function(err) {
643 this.cameraPresent = false;
644 },
645
646 /**
647 * Handles successful camera capture start.
648 * @private
649 */
650 handleVideoStarted_: function() {
651 $('user-image-preview').classList.add('online');
652 },
653
654 /**
655 * Handles camera stream update. Called regularly (at rate no greater then
656 * 4/sec) while camera stream is live.
657 * @private
658 */
659 handleVideoUpdate_: function() {
660 if (!this.lastFrameTime_) {
661 this.lastFrameTime_ = new Date().getTime();
662 return;
663 }
664 var newFrameTime = new Date().getTime();
665 this.lastFrameTime_ = newFrameTime;
666 },
667
668 /**
669 * Current image captured from camera as data URL. Setting to null will
670 * return to the live camera stream.
671 * @type {string=}
672 */
673 get cameraImage() {
674 return this.cameraImage_;
675 },
676 set cameraImage(imageUrl) {
677 this.cameraLive = !imageUrl;
678 var imageGrid = $('user-image-grid');
679 if (this.cameraPresent && !imageUrl) {
680 imageUrl = ButtonImages.TAKE_PHOTO;
681 }
682 if (imageUrl) {
683 this.cameraImage_ = this.cameraImage_ ?
684 imageGrid.updateItem(this.cameraImage_, imageUrl) :
685 imageGrid.addItem(imageUrl, undefined, undefined, 0);
686 this.cameraImage_.type = 'camera';
687 } else {
688 imageGrid.removeItem(this.cameraImage_);
689 this.cameraImage_ = null;
690 }
691 imageGrid.selectedItem = this.cameraImage_;
692 imageGrid.focus();
693 },
694
695 /**
696 * Updates user profile image.
697 * @param {?string} imageUrl Image encoded as data URL. If null, user has
698 * the default profile image, which we don't want to show.
699 * @private
700 */
701 setProfileImage_: function(imageUrl) {
702 this.profileImageLoading = false;
703 if (imageUrl !== null) {
704 this.profileImage_ =
705 $('user-image-grid').updateItem(this.profileImage_, imageUrl);
706 }
707 },
708
709 /**
710 * Appends received images to the list.
711 * @param {Array.<string>} images An array of URLs to user images.
712 * @private
713 */
714 setUserImages_: function(images) {
715 var imageGrid = $('user-image-grid');
716 for (var i = 0, url; url = images[i]; i++)
717 imageGrid.addItem(url).type = 'default';
718 },
719
720 /**
721 * Selects user image with the given URL.
722 * @param {string} url URL of the image to select.
723 * @private
724 */
725 setSelectedImage_: function(url) {
726 var imageGrid = $('user-image-grid');
727 imageGrid.selectedItemUrl = url;
728 imageGrid.focus();
729 },
730
731 /**
732 * Updates the image preview caption.
733 * @private
734 */
735 updateCaption_: function() {
736 $('user-image-preview-caption').textContent =
737 (this.selectionType == 'profile') ? this.profileImageCaption : '';
738 },
739
740 /**
741 * Updates localized content of the screen that is not updated via template.
742 */
743 updateLocalizedContent: function() {
744 this.updateProfileImageCaption_();
329 }, 745 },
330 746
331 /** 747 /**
332 * Updates profile image caption. 748 * Updates profile image caption.
333 * @private 749 * @private
334 */ 750 */
335 updateProfileImageCaption_: function() { 751 updateProfileImageCaption_: function() {
336 this.profileImageCaption = localStrings.getString( 752 this.profileImageCaption = localStrings.getString(
337 this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto'); 753 this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto');
338 } 754 }
339 }; 755 };
340 756
341 // Forward public APIs to private implementations. 757 // Forward public APIs to private implementations.
342 [ 758 [
343 'setCameraPresent', 759 'setCameraPresent',
Nikita (slow) 2012/06/07 15:26:55 setCameraPresent_ is not defined for UserImageScre
Ivan Korotkov 2012/06/09 14:19:01 It's not a problem as long as it doesn't get calle
344 'setProfileImage', 760 'setProfileImage',
345 'setSelectedImage', 761 'setSelectedImage',
346 'setUserImages', 762 'setUserImages',
347 'setUserPhoto', 763 'setUserPhoto',
348 ].forEach(function(name) { 764 ].forEach(function(name) {
349 UserImageScreen[name] = function(value) { 765 UserImageScreen[name] = function(value) {
350 $('user-image')[name + '_'](value); 766 $('user-image')[name + '_'](value);
351 }; 767 };
352 }); 768 });
353 769
354 return { 770 return {
355 UserImageScreen: UserImageScreen 771 UserImageScreen: UserImageScreen
356 }; 772 };
357 }); 773 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698