Commit d334146a authored by Kuo Jen Wei's avatar Kuo Jen Wei Committed by Commit Bot

Fix CCA selected resolution not shown bug

Bug: None

Change-Id: Ifbbbe1ad0c5f50ea7454e1256b0ba8f9cdce2e55
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1624365Reviewed-by: default avatarSheng-hao Tsao <shenghao@chromium.org>
Commit-Queue: Kuo Jen Wei <inker@chromium.org>
Cr-Commit-Position: refs/heads/master@{#662985}
parent f8df5d0f
...@@ -165,6 +165,7 @@ copy("chrome_camera_app_js_views_camera") { ...@@ -165,6 +165,7 @@ copy("chrome_camera_app_js_views_camera") {
"src/js/views/camera/options.js", "src/js/views/camera/options.js",
"src/js/views/camera/preview.js", "src/js/views/camera/preview.js",
"src/js/views/camera/recordtime.js", "src/js/views/camera/recordtime.js",
"src/js/views/camera/resolution_preference.js",
"src/js/views/camera/timertick.js", "src/js/views/camera/timertick.js",
] ]
......
...@@ -137,6 +137,7 @@ RESOURCES = \ ...@@ -137,6 +137,7 @@ RESOURCES = \
src/js/views/camera/options.js \ src/js/views/camera/options.js \
src/js/views/camera/preview.js \ src/js/views/camera/preview.js \
src/js/views/camera/recordtime.js \ src/js/views/camera/recordtime.js \
src/js/views/camera/resolution_preference.js \
src/js/views/camera/timertick.js \ src/js/views/camera/timertick.js \
src/js/views/dialog.js \ src/js/views/dialog.js \
src/js/views/gallery_base.js \ src/js/views/gallery_base.js \
......
...@@ -15,9 +15,9 @@ var cca = cca || {}; ...@@ -15,9 +15,9 @@ var cca = cca || {};
*/ */
/** /**
* A list of device id and the supported video, photo resolutions of its * Tuple of device id, width, height of preferred capture resolution and all
* corresponding video device. * available resolutions for a specific video device.
* @typedef {[number, ResolList, ResolList]} DeviceIdResols * @typedef {[string, number, number, ResolList]} DeviceIdResols
*/ */
/** /**
...@@ -31,7 +31,7 @@ cca.ResolutionEventBroker = function() { ...@@ -31,7 +31,7 @@ cca.ResolutionEventBroker = function() {
* @type {function(string, number, number)} * @type {function(string, number, number)}
* @private * @private
*/ */
this.photoChangeResolHandler_ = () => {}; this.photoChangePrefResolHandler_ = () => {};
/** /**
* Handler for requests of changing user-preferred resolution used in video * Handler for requests of changing user-preferred resolution used in video
...@@ -39,29 +39,39 @@ cca.ResolutionEventBroker = function() { ...@@ -39,29 +39,39 @@ cca.ResolutionEventBroker = function() {
* @type {function(string, number, number)} * @type {function(string, number, number)}
* @private * @private
*/ */
this.videoChangeResolHandler_ = () => {}; this.videoChangePrefResolHandler_ = () => {};
/** /**
* Listener for changes of resolution currently used in photo taking. * Listener for changes of device ids of currently available front, back and
* @type {function(string, number, number)} * external cameras and all of their supported photo resolutions.
* @type {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* @private * @private
*/ */
this.photoResolChangeListener_ = () => {}; this.photoResolChangeListener_ = () => {};
/** /**
* Listener for changes of resolution currently used in video recording. * Listener for changes of device ids of currently available front, back and
* @type {function(string, number, number)} * external cameras and all of their supported video resolutions.
* @type {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* @private * @private
*/ */
this.videoResolChangeListener_ = () => {}; this.videoResolChangeListener_ = () => {};
/** /**
* Listener for changes of currently available device-resolutions of front, * Listener for changes of preferred photo resolution used on particular video
* back, external cameras. * device.
* @type {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)} * @type {function(string, number, number)}
* @private
*/
this.photoPrefResolChangeListener_ = () => {};
/**
* Listener for changes of preferred video resolution used on particular video
* device.
* @type {function(string, number, number)}
* @private * @private
*/ */
this.deviceResolListener_ = () => {}; this.videoPrefResolChangeListener_ = () => {};
}; };
/** /**
...@@ -70,9 +80,9 @@ cca.ResolutionEventBroker = function() { ...@@ -70,9 +80,9 @@ cca.ResolutionEventBroker = function() {
* @param {function(string, number, number)} handler Called with device id of * @param {function(string, number, number)} handler Called with device id of
* video device to be changed and width, height of new resolution. * video device to be changed and width, height of new resolution.
*/ */
cca.ResolutionEventBroker.prototype.registerChangePhotoResolHandler = function( cca.ResolutionEventBroker.prototype.registerChangePhotoPrefResolHandler =
handler) { function(handler) {
this.photoChangeResolHandler_ = handler; this.photoChangePrefResolHandler_ = handler;
}; };
/** /**
...@@ -81,9 +91,9 @@ cca.ResolutionEventBroker.prototype.registerChangePhotoResolHandler = function( ...@@ -81,9 +91,9 @@ cca.ResolutionEventBroker.prototype.registerChangePhotoResolHandler = function(
* @param {function(string, number, number)} handler Called with device id of * @param {function(string, number, number)} handler Called with device id of
* video device to be changed and width, height of new resolution. * video device to be changed and width, height of new resolution.
*/ */
cca.ResolutionEventBroker.prototype.registerChangeVideoResolHandler = function( cca.ResolutionEventBroker.prototype.registerChangeVideoPrefResolHandler =
handler) { function(handler) {
this.videoChangeResolHandler_ = handler; this.videoChangePrefResolHandler_ = handler;
}; };
/** /**
...@@ -92,9 +102,9 @@ cca.ResolutionEventBroker.prototype.registerChangeVideoResolHandler = function( ...@@ -92,9 +102,9 @@ cca.ResolutionEventBroker.prototype.registerChangeVideoResolHandler = function(
* @param {number} width Change to resolution width. * @param {number} width Change to resolution width.
* @param {number} height Change to resolution height. * @param {number} height Change to resolution height.
*/ */
cca.ResolutionEventBroker.prototype.requestChangePhotoResol = function( cca.ResolutionEventBroker.prototype.requestChangePhotoPrefResol = function(
deviceId, width, height) { deviceId, width, height) {
this.photoChangeResolHandler_(deviceId, width, height); this.photoChangePrefResolHandler_(deviceId, width, height);
}; };
/** /**
...@@ -103,15 +113,16 @@ cca.ResolutionEventBroker.prototype.requestChangePhotoResol = function( ...@@ -103,15 +113,16 @@ cca.ResolutionEventBroker.prototype.requestChangePhotoResol = function(
* @param {number} width Change to resolution width. * @param {number} width Change to resolution width.
* @param {number} height Change to resolution height. * @param {number} height Change to resolution height.
*/ */
cca.ResolutionEventBroker.prototype.requestChangeVideoResol = function( cca.ResolutionEventBroker.prototype.requestChangeVideoPrefResol = function(
deviceId, width, height) { deviceId, width, height) {
this.videoChangeResolHandler_(deviceId, width, height); this.videoChangePrefResolHandler_(deviceId, width, height);
}; };
/** /**
Adds listener for changes of resolution currently used in photo taking. * Adds listener for changes of available photo resolutions of all cameras.
* @param {function(string, number, number)} listener Called with changed device * @param {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* id and new resolution width, height. * listener Called with device id, preferred photo capture resolution and
* available photo resolutions of front, back and external cameras.
*/ */
cca.ResolutionEventBroker.prototype.addPhotoResolChangeListener = function( cca.ResolutionEventBroker.prototype.addPhotoResolChangeListener = function(
listener) { listener) {
...@@ -119,9 +130,10 @@ cca.ResolutionEventBroker.prototype.addPhotoResolChangeListener = function( ...@@ -119,9 +130,10 @@ cca.ResolutionEventBroker.prototype.addPhotoResolChangeListener = function(
}; };
/** /**
* Adds listener for changes of resolution currently used in video recording. * Adds listener for changes of available video resolutions of all cameras.
* @param {function(string, number, number)} listener Called with changed device * @param {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* id and new resolution width, height. * listener Called with device id, preferred video capture resolution and
* available video resolutions of front, back and external cameras.
*/ */
cca.ResolutionEventBroker.prototype.addVideoResolChangeListener = function( cca.ResolutionEventBroker.prototype.addVideoResolChangeListener = function(
listener) { listener) {
...@@ -129,47 +141,77 @@ cca.ResolutionEventBroker.prototype.addVideoResolChangeListener = function( ...@@ -129,47 +141,77 @@ cca.ResolutionEventBroker.prototype.addVideoResolChangeListener = function(
}; };
/** /**
* Notifies the change of resolution currently used in photo taking. * Notifies the change of available photo resolution of all cameras.
* @param {string} deviceId Device id of changed video device. * @param {?DeviceIdResols} frontResolutions Device id of front camera and
* @param {number} width New resolution width. * all of its available supported photo resolutions.
* @param {number} height New resolution height. * @param {?DeviceIdResols} backResolutions Device id of back camera and
* all of its available supported photo resolutions.
* @param {Array<DeviceIdResols>} externalResolutions Device id of external
* cameras and all of their available supported photo resolutions.
*/ */
cca.ResolutionEventBroker.prototype.notifyPhotoResolChange = function( cca.ResolutionEventBroker.prototype.notifyPhotoResolChange = function(
deviceId, width, height) { frontResolutions, backResolutions, externalResolutions) {
this.photoResolChangeListener_(deviceId, width, height); this.photoResolChangeListener_(
frontResolutions, backResolutions, externalResolutions);
}; };
/** /**
* Notifies the change of resolution currently used in video recording. * Notifies the change of available video resolution of all cameras.
* @param {string} deviceId Device id of changed video device. * @param {?DeviceIdResols} frontResolutions Device id of front camera and
* @param {number} width New resolution width. * all of its available supported video resolutions.
* @param {number} height New resolution height. * @param {?DeviceIdResols} backResolutions Device id of back camera and
* all of its available supported video resolutions.
* @param {Array<DeviceIdResols>} externalResolutions Device id of external
* cameras and all of their available supported video resolutions.
*/ */
cca.ResolutionEventBroker.prototype.notifyVideoResolChange = function( cca.ResolutionEventBroker.prototype.notifyVideoResolChange = function(
deviceId, width, height) { frontResolutions, backResolutions, externalResolutions) {
this.videoResolChangeListener_(deviceId, width, height); this.videoResolChangeListener_(
frontResolutions, backResolutions, externalResolutions);
}; };
/** /**
* Adds listener for changes of currently available device-resolutions of * Adds listener for changes of preferred resolution used in taking photo on
* all cameras. * particular video device.
* @param {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)} * @param {function(string, number, number)} listener Called with changed video
* listener Listener function called with deviceId-resolutions of front, * device id and new preferred resolution width, height.
* back and external cameras. */
cca.ResolutionEventBroker.prototype.addPhotoPrefResolChangeListener = function(
listener) {
this.photoPrefResolChangeListener_ = listener;
};
/**
* Adds listener for changes of preferred resolution used in video recording on
* particular video device.
* @param {function(string, number, number)} listener Called with changed video
* device id and new preferred resolution width, height.
*/
cca.ResolutionEventBroker.prototype.addVideoPrefResolChangeListener = function(
listener) {
this.videoPrefResolChangeListener_ = listener;
};
/**
* Notifies the change of preferred resolution used in photo taking on
* particular video device.
* @param {string} deviceId Device id of changed video device.
* @param {number} width New resolution width.
* @param {number} height New resolution height.
*/ */
cca.ResolutionEventBroker.prototype.addUpdateDeviceResolutionsListener = cca.ResolutionEventBroker.prototype.notifyPhotoPrefResolChange = function(
function(listener) { deviceId, width, height) {
this.deviceResolListener_ = listener; this.photoPrefResolChangeListener_(deviceId, width, height);
}; };
/** /**
* Notifies the change of currently available device-resolutions of all cameras. * Notifies the change of preferred resolution used in video recording on
* @param {?DeviceIdResols} front DeviceId-resolutions of front camera. * particular video device.
* @param {?DeviceIdResols} back DeviceId-resolutions of back camera. * @param {string} deviceId Device id of changed video device.
* @param {Array<DeviceIdResols>} externals DeviceId-resolutions of external * @param {number} width New resolution width.
* cameras. * @param {number} height New resolution height.
*/ */
cca.ResolutionEventBroker.prototype.notifyUpdateDeviceResolutions = function( cca.ResolutionEventBroker.prototype.notifyVideoPrefResolChange = function(
front, back, externals) { deviceId, width, height) {
this.deviceResolListener_(front, back, externals); this.videoPrefResolChangeListener_(deviceId, width, height);
}; };
...@@ -30,6 +30,20 @@ cca.views.Camera = function(model, resolBroker) { ...@@ -30,6 +30,20 @@ cca.views.Camera = function(model, resolBroker) {
*/ */
this.model_ = model; this.model_ = model;
/**
* @type {cca.views.camera.PhotoResolPreferrer}
* @private
*/
this.photoResolPreferrer_ = new cca.views.camera.PhotoResolPreferrer(
resolBroker, this.stop_.bind(this));
/**
* @type {cca.views.camera.VideoResolPreferrer}
* @private
*/
this.videoResolPreferrer_ = new cca.views.camera.VideoResolPreferrer(
resolBroker, this.stop_.bind(this));
/** /**
* Layout handler for the camera view. * Layout handler for the camera view.
* @type {cca.views.camera.Layout} * @type {cca.views.camera.Layout}
...@@ -49,8 +63,9 @@ cca.views.Camera = function(model, resolBroker) { ...@@ -49,8 +63,9 @@ cca.views.Camera = function(model, resolBroker) {
* @type {cca.views.camera.Options} * @type {cca.views.camera.Options}
* @private * @private
*/ */
this.options_ = this.options_ = new cca.views.camera.Options(
new cca.views.camera.Options(resolBroker, this.stop_.bind(this)); this.photoResolPreferrer_, this.videoResolPreferrer_,
this.stop_.bind(this));
/** /**
* Modes for the camera. * Modes for the camera.
...@@ -58,8 +73,8 @@ cca.views.Camera = function(model, resolBroker) { ...@@ -58,8 +73,8 @@ cca.views.Camera = function(model, resolBroker) {
* @private * @private
*/ */
this.modes_ = new cca.views.camera.Modes( this.modes_ = new cca.views.camera.Modes(
resolBroker, this.stop_.bind(this), this.stop_.bind(this), this.photoResolPreferrer_, this.videoResolPreferrer_,
async (blob, isMotionPicture, filename) => { this.stop_.bind(this), async (blob, isMotionPicture, filename) => {
if (blob) { if (blob) {
cca.metrics.log( cca.metrics.log(
cca.metrics.Type.CAPTURE, this.facingMode_, blob.mins); cca.metrics.Type.CAPTURE, this.facingMode_, blob.mins);
...@@ -225,10 +240,9 @@ cca.views.Camera.prototype.stop_ = function() { ...@@ -225,10 +240,9 @@ cca.views.Camera.prototype.stop_ = function() {
cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) { cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) {
let supportedModes = null; let supportedModes = null;
for (const mode of this.modes_.getModeCandidates()) { for (const mode of this.modes_.getModeCandidates()) {
const [photoRs, videoRs] = const previewRs = (await this.options_.getDeviceResolutions(deviceId))[1];
await this.options_.getDeviceResolutions(deviceId);
for (const [[width, height], previewCandidates] of this.modes_ for (const [[width, height], previewCandidates] of this.modes_
.getResolutionCandidates(mode, deviceId, photoRs, videoRs)) { .getResolutionCandidates(mode, deviceId, previewRs)) {
for (const constraints of previewCandidates) { for (const constraints of previewCandidates) {
try { try {
const stream = await navigator.mediaDevices.getUserMedia(constraints); const stream = await navigator.mediaDevices.getUserMedia(constraints);
......
...@@ -21,16 +21,15 @@ cca.views.camera = cca.views.camera || {}; ...@@ -21,16 +21,15 @@ cca.views.camera = cca.views.camera || {};
/** /**
* Mode controller managing capture sequence of different camera mode. * Mode controller managing capture sequence of different camera mode.
* @param {cca.ResolutionEventBroker} resolBroker * @param {cca.views.camera.PhotoResolPreferrer} photoResolPreferrer
* @param {function()} doSwitchResolution Callback to trigger resolution * @param {cca.views.camera.VideoResolPreferrer} videoResolPreferrer
* switching.
* @param {function()} doSwitchMode Callback to trigger mode switching. * @param {function()} doSwitchMode Callback to trigger mode switching.
* @param {function(?Blob, boolean, string): Promise} doSavePicture Callback for * @param {function(?Blob, boolean, string): Promise} doSavePicture Callback for
* saving picture. * saving picture.
* @constructor * @constructor
*/ */
cca.views.camera.Modes = function( cca.views.camera.Modes = function(
resolBroker, doSwitchResolution, doSwitchMode, doSavePicture) { photoResolPreferrer, videoResolPreferrer, doSwitchMode, doSavePicture) {
/** /**
* @type {function()} * @type {function()}
* @private * @private
...@@ -63,20 +62,6 @@ cca.views.camera.Modes = function( ...@@ -63,20 +62,6 @@ cca.views.camera.Modes = function(
*/ */
this.modesGroup_ = document.querySelector('#modes-group'); this.modesGroup_ = document.querySelector('#modes-group');
/**
* @type {cca.views.camera.PhotoResolutionConfig}
* @private
*/
this.photoResConfig_ = new cca.views.camera.PhotoResolutionConfig(
resolBroker, doSwitchResolution);
/**
* @type {cca.views.camera.VideoResolutionConfig}
* @private
*/
this.videoResConfig_ = new cca.views.camera.VideoResolutionConfig(
resolBroker, doSwitchResolution);
/** /**
* Captured resolution width. * Captured resolution width.
* @type {number} * @type {number}
...@@ -101,7 +86,7 @@ cca.views.camera.Modes = function( ...@@ -101,7 +86,7 @@ cca.views.camera.Modes = function(
captureFactory: () => captureFactory: () =>
new cca.views.camera.Video(this.stream_, this.doSavePicture_), new cca.views.camera.Video(this.stream_, this.doSavePicture_),
isSupported: async () => true, isSupported: async () => true,
resolutionConfig: this.videoResConfig_, resolutionConfig: videoResolPreferrer,
nextMode: 'photo-mode', nextMode: 'photo-mode',
}, },
'photo-mode': { 'photo-mode': {
...@@ -109,7 +94,7 @@ cca.views.camera.Modes = function( ...@@ -109,7 +94,7 @@ cca.views.camera.Modes = function(
this.stream_, this.doSavePicture_, this.captureWidth_, this.stream_, this.doSavePicture_, this.captureWidth_,
this.captureHeight_), this.captureHeight_),
isSupported: async () => true, isSupported: async () => true,
resolutionConfig: this.photoResConfig_, resolutionConfig: photoResolPreferrer,
nextMode: 'square-mode', nextMode: 'square-mode',
}, },
'square-mode': { 'square-mode': {
...@@ -117,7 +102,7 @@ cca.views.camera.Modes = function( ...@@ -117,7 +102,7 @@ cca.views.camera.Modes = function(
this.stream_, this.doSavePicture_, this.captureWidth_, this.stream_, this.doSavePicture_, this.captureWidth_,
this.captureHeight_), this.captureHeight_),
isSupported: async () => true, isSupported: async () => true,
resolutionConfig: this.photoResConfig_, resolutionConfig: photoResolPreferrer,
nextMode: 'portrait-mode', nextMode: 'portrait-mode',
}, },
'portrait-mode': { 'portrait-mode': {
...@@ -139,7 +124,7 @@ cca.views.camera.Modes = function( ...@@ -139,7 +124,7 @@ cca.views.camera.Modes = function(
return false; return false;
} }
}, },
resolutionConfig: this.photoResConfig_, resolutionConfig: photoResolPreferrer,
nextMode: 'video-mode', nextMode: 'video-mode',
}, },
}; };
...@@ -183,287 +168,6 @@ cca.views.camera.Modes.prototype.updateModeUI_ = function(mode) { ...@@ -183,287 +168,6 @@ cca.views.camera.Modes.prototype.updateModeUI_ = function(mode) {
}); });
}; };
/**
* Controller for generating suitable capture and preview resolution
* combination.
* @param {cca.ResolutionEventBroker} resolBroker
* @param {function()} doSwitchResolution
* @constructor
*/
cca.views.camera.ModeResolutionConfig = function(
resolBroker, doSwitchResolution) {
/**
* @type {cca.ResolutionEventBroker}
* @private
*/
this.resolBroker_ = resolBroker;
/**
* @type {function()}
* @private
*/
this.doSwitchResolution_ = doSwitchResolution;
/**
* Object saving resolution preference for each of its key as device id and
* value to be preferred width, height of resolution of that video device.
* @type {Object<string, {width: number, height: number}>}
* @private
*/
this.prefResolution_ = null;
/**
* Device id of currently working video device.
* @type {string}
* @private
*/
this.deviceId_ = null;
// End of properties, seal the object.
Object.seal(this);
};
/**
* Gets all available resolutions candidates for capturing under this controller
* and its corresponding preview constraints from all available resolutions of
* different format. Returned resolutions and constraints candidates are both
* sorted in desired trying order.
* @param {string} deviceId
* @param {ResolList} photoResolutions
* @param {ResolList} videoResolutions
* @return {Array<[number, number, Array<Object>]>} Result capture resolution
* width, height and constraints-candidates for its preview.
*/
cca.views.camera.ModeResolutionConfig.prototype.getSortedCandidates = function(
deviceId, photoResolutions, videoResolutions) {
return null;
};
/**
* Updates selected resolution.
* @param {string} deviceId Device id of selected video device.
* @param {number} width Width of selected resolution.
* @param {number} height Height of selected resolution.
*/
cca.views.camera.ModeResolutionConfig.prototype.updateSelectedResolution =
function(deviceId, width, height) {};
/**
* Controller for generating suitable video recording and preview resolution
* combination.
* @param {cca.ResolutionEventBroker} resolBroker
* @param {function()} doSwitchResolution
* @constructor
*/
cca.views.camera.VideoResolutionConfig = function(
resolBroker, doSwitchResolution) {
cca.views.camera.ModeResolutionConfig.call(
this, resolBroker, doSwitchResolution);
// Restore saved preferred video resolution per video device.
chrome.storage.local.get(
{deviceVideoResolution: {}},
(values) => this.prefResolution_ = values.deviceVideoResolution);
this.resolBroker_.registerChangeVideoResolHandler(
(deviceId, width, height) => {
this.prefResolution_[deviceId] = {width, height};
chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
if (cca.state.get('video-mode') && deviceId == this.deviceId_) {
this.doSwitchResolution_();
}
});
};
cca.views.camera.VideoResolutionConfig.prototype = {
__proto__: cca.views.camera.ModeResolutionConfig.prototype,
};
/**
* @param {string} deviceId
* @param {number} width
* @param {number} height
* @override
*/
cca.views.camera.VideoResolutionConfig.prototype.updateSelectedResolution =
function(deviceId, width, height) {
this.deviceId_ = deviceId;
this.prefResolution_[deviceId] = {width, height};
chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
this.resolBroker_.notifyVideoResolChange(deviceId, width, height);
};
/**
* @param {string} deviceId
* @param {ResolList} photoResolutions
* @param {ResolList} videoResolutions
* @return {Array<[number, number, Array<Object>]>}
* @override
*/
cca.views.camera.VideoResolutionConfig.prototype.getSortedCandidates = function(
deviceId, photoResolutions, videoResolutions) {
// Due to the limitation of MediaStream API, preview stream is used directly
// to do video recording.
const prefR = this.prefResolution_[deviceId] || {width: 0, height: -1};
const preferredOrder = ([w, h], [w2, h2]) => {
if (w == w2 && h == h2) {
return 0;
}
// Exactly the preferred resolution.
if (w == prefR.width && h == prefR.height) {
return -1;
}
if (w2 == prefR.width && h2 == prefR.height) {
return 1;
}
// Aspect ratio same as preferred resolution.
if (w * h2 != w2 * h) {
if (w * prefR.height == prefR.width * h) {
return -1;
}
if (w2 * prefR.height == prefR.width * h2) {
return 1;
}
}
return w2 * h2 - w * h;
};
return videoResolutions.sort(preferredOrder).map(([width, height]) => ([
[width, height],
[{
audio: true,
video: {
deviceId:
{exact: deviceId},
frameRate: {min: 24},
width,
height,
},
}],
]));
};
/**
* Controller for generating suitable photo taking and preview resolution
* combination.
* @param {cca.ResolutionEventBroker} resolBroker
* @param {function()} doSwitchResolution
* @constructor
*/
cca.views.camera.PhotoResolutionConfig = function(
resolBroker, doSwitchResolution) {
cca.views.camera.ModeResolutionConfig.call(
this, resolBroker, doSwitchResolution);
// Restore saved preferred photo resolution per video device.
chrome.storage.local.get(
{devicePhotoResolution: {}},
(values) => this.prefResolution_ = values.devicePhotoResolution);
this.resolBroker_.registerChangePhotoResolHandler(
(deviceId, width, height) => {
this.prefResolution_[deviceId] = {width, height};
chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
if (!cca.state.get('video-mode') && deviceId == this.deviceId_) {
this.doSwitchResolution_();
}
});
};
/**
* @param {string} deviceId
* @param {number} width
* @param {number} height
* @override
*/
cca.views.camera.PhotoResolutionConfig.prototype.updateSelectedResolution =
function(deviceId, width, height) {
this.deviceId_ = deviceId;
this.prefResolution_[deviceId] = {width, height};
chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
this.resolBroker_.notifyPhotoResolChange(deviceId, width, height);
};
/**
* Finds and pairs photo resolutions and preview resolutions with the same
* aspect ratio.
* @param {ResolList} captureResolutions Available photo capturing resolutions.
* @param {ResolList} previewResolutions Available preview resolutions.
* @return {Array<[ResolList, ResolList]>} Each item of returned array is a pair
* of capture and preview resolutions of same aspect ratio.
*/
cca.views.camera.PhotoResolutionConfig.prototype
.pairCapturePreviewResolutions_ = function(
captureResolutions, previewResolutions) {
const toAspectRatio = (w, h) => (w / h).toFixed(4);
const previewRatios = previewResolutions.reduce((rs, [w, h]) => {
const key = toAspectRatio(w, h);
rs[key] = rs[key] || [];
rs[key].push([w, h]);
return rs;
}, {});
const captureRatios = captureResolutions.reduce((rs, [w, h]) => {
const key = toAspectRatio(w, h);
if (key in previewRatios) {
rs[key] = rs[key] || [];
rs[key].push([w, h]);
}
return rs;
}, {});
return Object.entries(captureRatios)
.map(([aspectRatio,
captureRs]) => [captureRs, previewRatios[aspectRatio]]);
};
/**
* @param {string} deviceId
* @param {ResolList} photoResolutions
* @param {ResolList} videoResolutions
* @return {Array<[number, number, Array<Object>]>}
* @override
*/
cca.views.camera.PhotoResolutionConfig.prototype.getSortedCandidates = function(
deviceId, photoResolutions, videoResolutions) {
const prefR = this.prefResolution_[deviceId] || {width: 0, height: -1};
return this.pairCapturePreviewResolutions_(photoResolutions, videoResolutions)
.map(([captureRs, previewRs]) => {
if (captureRs.some(([w, h]) => w == prefR.width && h == prefR.height)) {
var captureR = [prefR.width, prefR.height];
} else {
var captureR = captureRs.reduce(
(captureR, r) => (r[0] > captureR[0] ? r : captureR), [0, -1]);
}
const candidates = previewRs.sort(([w, h], [w2, h2]) => w2 - w)
.map(([width, height]) => ({
audio: false,
video: {
deviceId: {exact: deviceId},
frameRate: {min: 24},
width,
height,
},
}));
// Format of map result:
// [
// [[CaptureW 1, CaptureH 1], [CaptureW 2, CaptureH 2], ...],
// [PreviewConstraint 1, PreviewConstraint 2, ...]
// ]
return [captureR, candidates];
})
.sort(([[w, h]], [[w2, h2]]) => {
if (w == w2 && h == h2) {
return 0;
}
if (w == prefR.width && h == prefR.height) {
return -1;
}
if (w2 == prefR.width && h2 == prefR.height) {
return 1;
}
return w2 * h2 - w * h;
});
};
/** /**
* Switches mode to either video-recording or photo-taking. * Switches mode to either video-recording or photo-taking.
* @param {string} mode Class name of the switching mode. * @param {string} mode Class name of the switching mode.
...@@ -500,15 +204,14 @@ cca.views.camera.Modes.prototype.getModeCandidates = function() { ...@@ -500,15 +204,14 @@ cca.views.camera.Modes.prototype.getModeCandidates = function() {
* constraints for the given mode. * constraints for the given mode.
* @param {string} mode * @param {string} mode
* @param {string} deviceId * @param {string} deviceId
* @param {ResolList} photoResolutions * @param {ResolList} previewResolutions
* @param {ResolList} videoResolutions
* @return {Array<[number, number, Array<Object>]>} Result capture resolution * @return {Array<[number, number, Array<Object>]>} Result capture resolution
* width, height and constraints-candidates for its preview. * width, height and constraints-candidates for its preview.
*/ */
cca.views.camera.Modes.prototype.getResolutionCandidates = function( cca.views.camera.Modes.prototype.getResolutionCandidates = function(
mode, deviceId, photoResolutions, videoResolutions) { mode, deviceId, previewResolutions) {
return this.allModes_[mode].resolutionConfig.getSortedCandidates( return this.allModes_[mode].resolutionConfig.getSortedCandidates(
deviceId, photoResolutions, videoResolutions); deviceId, previewResolutions);
}; };
/** /**
...@@ -559,7 +262,7 @@ cca.views.camera.Modes.prototype.updateMode = ...@@ -559,7 +262,7 @@ cca.views.camera.Modes.prototype.updateMode =
this.captureWidth_ = captureWidth; this.captureWidth_ = captureWidth;
this.captureHeight_ = captureHeight; this.captureHeight_ = captureHeight;
this.current = this.allModes_[mode].captureFactory(); this.current = this.allModes_[mode].captureFactory();
this.allModes_[mode].resolutionConfig.updateSelectedResolution( this.allModes_[mode].resolutionConfig.updateCurrentResolution(
deviceId, captureWidth, captureHeight); deviceId, captureWidth, captureHeight);
}; };
......
...@@ -21,16 +21,24 @@ cca.views.camera = cca.views.camera || {}; ...@@ -21,16 +21,24 @@ cca.views.camera = cca.views.camera || {};
/** /**
* Creates a controller for the options of Camera view. * Creates a controller for the options of Camera view.
* @param {cca.ResolutionEventBroker} resolBroker * @param {cca.views.camera.PhotoResolPreferrer} photoResolPreferrer
* @param {cca.views.camera.VideoResolPreferrer} videoResolPreferrer
* @param {function()} doSwitchDevice Callback to trigger device switching. * @param {function()} doSwitchDevice Callback to trigger device switching.
* @constructor * @constructor
*/ */
cca.views.camera.Options = function(resolBroker, doSwitchDevice) { cca.views.camera.Options = function(
photoResolPreferrer, videoResolPreferrer, doSwitchDevice) {
/** /**
* @type {cca.ResolutionEventBroker} * @type {cca.views.camera.PhotoResolPreferrer}
* @private * @private
*/ */
this.resolBroker_ = resolBroker; this.photoResolPreferrer_ = photoResolPreferrer;
/**
* @type {cca.views.camera.VideoResolPreferrer}
* @private
*/
this.videoResolPreferrer_ = videoResolPreferrer;
/** /**
* @type {function()} * @type {function()}
...@@ -132,13 +140,6 @@ cca.views.camera.Options.FRONT_CAMERA_LABEL = 'Front Camera'; ...@@ -132,13 +140,6 @@ cca.views.camera.Options.FRONT_CAMERA_LABEL = 'Front Camera';
*/ */
cca.views.camera.Options.BACK_CAMERA_LABEL = 'Back Camera'; cca.views.camera.Options.BACK_CAMERA_LABEL = 'Back Camera';
/**
* Label of external facing camera from MediaDeviceInfo.
* @type {string}
* @const
*/
cca.views.camera.Options.EXTERNAL_CAMERA_LABEL = 'External Camera';
/** /**
* Switches to the next available camera device. * Switches to the next available camera device.
* @private * @private
...@@ -300,15 +301,19 @@ cca.views.camera.Options.prototype.maybeRefreshVideoDeviceIds_ = function() { ...@@ -300,15 +301,19 @@ cca.views.camera.Options.prototype.maybeRefreshVideoDeviceIds_ = function() {
case cca.views.camera.Options.BACK_CAMERA_LABEL: case cca.views.camera.Options.BACK_CAMERA_LABEL:
backSetting = setting; backSetting = setting;
break; break;
case cca.views.camera.Options.EXTERNAL_CAMERA_LABEL:
externalSettings.push(setting);
break;
default: default:
console.error(`Ignore device of unknown label: ${label}`); // TODO(inker): Use private API to get camera facing information.
externalSettings.push(setting);
} }
}); });
this.resolBroker_.notifyUpdateDeviceResolutions( this.photoResolPreferrer_.updateResolutions(
frontSetting, backSetting, externalSettings); frontSetting && [frontSetting[0], frontSetting[1]],
backSetting && [backSetting[0], backSetting[1]],
externalSettings.map(([deviceId, photoRs]) => [deviceId, photoRs]));
this.videoResolPreferrer_.updateResolutions(
frontSetting && [frontSetting[0], frontSetting[2]],
backSetting && [backSetting[0], backSetting[2]],
externalSettings.map(([deviceId, , videoRs]) => [deviceId, videoRs]));
}); });
}; };
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Namespace for the Camera app.
*/
var cca = cca || {};
/**
* Namespace for views.
*/
cca.views = cca.views || {};
/**
* Namespace for Camera view.
*/
cca.views.camera = cca.views.camera || {};
/**
* Controller for handling resolution preference.
* @param {cca.ResolutionEventBroker} resolBroker
* @param {function()} doSwitchResolution
* @constructor
*/
cca.views.camera.ResolutionPreferrer = function(
resolBroker, doSwitchResolution) {
/**
* @type {cca.ResolutionEventBroker}
* @protected
*/
this.resolBroker_ = resolBroker;
/**
* @type {function()}
* @protected
*/
this.doSwitchResolution_ = doSwitchResolution;
/**
* Object saving resolution preference that each of its key as device id and
* value to be preferred width, height of resolution of that video device.
* @type {?Object<string, {width: number, height: number}>}
* @protected
*/
this.prefResolution_ = null;
/**
* Device id of currently working video device.
* @type {?string}
* @protected
*/
this.deviceId_ = null;
/**
* Object of device id as its key and all of available capture resolutions
* supported by that video device as its value.
* @type {Object<string, ResolList>}
* @protected
*/
this.deviceResolutions_ = null;
// End of properties, seal the object.
Object.seal(this);
};
/**
* Updates resolution preference based on newly updated available resolutions.
* @param {?[string, ResolList]} frontResolutions Device id and available
* resolutions of front camera.
* @param {?[string, ResolList]} backResolutions Device id and available
* resolutions of back camera.
* @param {Array<[string, ResolList]>} externalResolutions Device ids and
* available resolutions of all external cameras.
*/
cca.views.camera.ResolutionPreferrer.prototype.updateResolutions = function(
frontResolutions, backResolutions, externalResolutions) {};
/**
* Updates preferred resolution of currently working video device.
* @param {string} deviceId Device id of video device to be updated.
* @param {number} width Width of resolution to be updated to.
* @param {number} height Height of resolution to be updated to.
*/
cca.views.camera.ResolutionPreferrer.prototype.updateCurrentResolution =
function(deviceId, width, height) {};
/**
* Gets all available resolutions candidates for capturing under this controller
* and its corresponding preview constraints for the specified video device.
* Returned resolutions and constraints candidates are both sorted in desired
* trying order.
* @param {string} deviceId Device id of video device.
* @param {ResolList} previewResolutions Available preview resolutions for the
* video device.
* @return {Array<[number, number, Array<Object>]>} Result capture resolution
* width, height and constraints-candidates for its preview.
*/
cca.views.camera.ResolutionPreferrer.prototype.getSortedCandidates = function(
deviceId, previewResolutions) {
return null;
};
/**
* Controller for handling video resolution preference.
* @param {cca.ResolutionEventBroker} resolBroker
* @param {function()} doSwitchResolution
* @constructor
*/
cca.views.camera.VideoResolPreferrer = function(
resolBroker, doSwitchResolution) {
cca.views.camera.ResolutionPreferrer.call(
this, resolBroker, doSwitchResolution);
// Restore saved preferred video resolution per video device.
chrome.storage.local.get(
{deviceVideoResolution: {}},
(values) => this.prefResolution_ = values.deviceVideoResolution);
this.resolBroker_.registerChangeVideoPrefResolHandler(
(deviceId, width, height) => {
this.prefResolution_[deviceId] = {width, height};
chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
if (cca.state.get('video-mode') && deviceId == this.deviceId_) {
this.doSwitchResolution_();
} else {
this.resolBroker_.notifyVideoPrefResolChange(deviceId, width, height);
}
});
};
cca.views.camera.VideoResolPreferrer.prototype = {
__proto__: cca.views.camera.ResolutionPreferrer.prototype,
};
/**
* @param {?[string, ResolList]} frontResolutions
* @param {?[string, ResolList]} backResolutions
* @param {Array<[string, ResolList]>} externalResolutions
* @override
*/
cca.views.camera.VideoResolPreferrer.prototype.updateResolutions = function(
frontResolutions, backResolutions, externalResolutions) {
this.deviceResolutions_ = {};
const toDeviceIdResols = (deviceId, resolutions) => {
this.deviceResolutions_[deviceId] = resolutions;
let {width = -1, height = -1} = this.prefResolution_[deviceId] || {};
if (!resolutions.find(([w, h]) => w == width && h == height)) {
[width, height] = resolutions.reduce(
(maxR, R) => (maxR[0] * maxR[1] < R[0] * R[1] ? R : maxR), [0, 0]);
}
this.prefResolution_[deviceId] = {width, height};
return [deviceId, width, height, resolutions];
};
this.resolBroker_.notifyVideoResolChange(
frontResolutions && toDeviceIdResols(...frontResolutions),
backResolutions && toDeviceIdResols(...backResolutions),
externalResolutions.map((ext) => toDeviceIdResols(...ext)));
chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
};
/**
* @param {string} deviceId
* @param {number} width
* @param {number} height
* @override
*/
cca.views.camera.VideoResolPreferrer.prototype.updateCurrentResolution =
function(deviceId, width, height) {
this.deviceId_ = deviceId;
this.prefResolution_[deviceId] = {width, height};
chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
this.resolBroker_.notifyVideoPrefResolChange(deviceId, width, height);
};
/**
* @param {string} deviceId
* @return {Array<[number, number, Array<Object>]>}
* @override
*/
cca.views.camera.VideoResolPreferrer.prototype.getSortedCandidates = function(
deviceId, previewResolutions) {
// Due to the limitation of MediaStream API, preview stream is used directly
// to do video recording.
const prefR = this.prefResolution_[deviceId] || {width: 0, height: -1};
const preferredOrder = ([w, h], [w2, h2]) => {
if (w == w2 && h == h2) {
return 0;
}
// Exactly the preferred resolution.
if (w == prefR.width && h == prefR.height) {
return -1;
}
if (w2 == prefR.width && h2 == prefR.height) {
return 1;
}
// Aspect ratio same as preferred resolution.
if (w * h2 != w2 * h) {
if (w * prefR.height == prefR.width * h) {
return -1;
}
if (w2 * prefR.height == prefR.width * h2) {
return 1;
}
}
return w2 * h2 - w * h;
};
const toConstraints = (width, height) => ({
audio: true,
video: {
deviceId: {exact: deviceId},
frameRate: {min: 24},
width,
height,
},
});
const videoResolutions = this.deviceResolutions_[deviceId];
return videoResolutions.sort(preferredOrder).map(([width, height]) => ([
[width, height],
[toConstraints(
width, height)],
]));
};
/**
* Controller for handling photo resolution preference.
* @param {cca.ResolutionEventBroker} resolBroker
* @param {function()} doSwitchResolution
* @constructor
*/
cca.views.camera.PhotoResolPreferrer = function(
resolBroker, doSwitchResolution) {
cca.views.camera.ResolutionPreferrer.call(
this, resolBroker, doSwitchResolution);
// Restore saved preferred photo resolution per video device.
chrome.storage.local.get(
{devicePhotoResolution: {}},
(values) => this.prefResolution_ = values.devicePhotoResolution);
this.resolBroker_.registerChangePhotoPrefResolHandler(
(deviceId, width, height) => {
this.prefResolution_[deviceId] = {width, height};
chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
if (!cca.state.get('video-mode') && deviceId == this.deviceId_) {
this.doSwitchResolution_();
} else {
this.resolBroker_.notifyPhotoPrefResolChange(deviceId, width, height);
}
});
};
cca.views.camera.PhotoResolPreferrer.prototype = {
__proto__: cca.views.camera.ResolutionPreferrer.prototype,
};
/**
* @param {?[string, ResolList]} frontResolutions
* @param {?[string, ResolList]} backResolutions
* @param {Array<[string, ResolList]>} externalResolutions
* @override
*/
cca.views.camera.PhotoResolPreferrer.prototype.updateResolutions = function(
frontResolutions, backResolutions, externalResolutions) {
this.deviceResolutions_ = {};
const toDeviceIdResols = (deviceId, resolutions) => {
this.deviceResolutions_[deviceId] = resolutions;
let {width = -1, height = -1} = this.prefResolution_[deviceId] || {};
if (!resolutions.find(([w, h]) => w == width && h == height)) {
[width, height] = resolutions.reduce(
(maxR, R) => (maxR[0] * maxR[1] < R[0] * R[1] ? R : maxR), [0, 0]);
}
this.prefResolution_[deviceId] = {width, height};
return [deviceId, width, height, resolutions];
};
this.resolBroker_.notifyPhotoResolChange(
frontResolutions && toDeviceIdResols(...frontResolutions),
backResolutions && toDeviceIdResols(...backResolutions),
externalResolutions.map((ext) => toDeviceIdResols(...ext)));
chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
};
/**
* @param {string} deviceId
* @param {number} width
* @param {number} height
* @override
*/
cca.views.camera.PhotoResolPreferrer.prototype.updateCurrentResolution =
function(deviceId, width, height) {
this.deviceId_ = deviceId;
this.prefResolution_[deviceId] = {width, height};
chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
this.resolBroker_.notifyPhotoPrefResolChange(deviceId, width, height);
};
/**
* Finds and pairs photo resolutions and preview resolutions with the same
* aspect ratio.
* @param {ResolList} captureResolutions Available photo capturing resolutions.
* @param {ResolList} previewResolutions Available preview resolutions.
* @return {Array<[ResolList, ResolList]>} Each item of returned array is a pair
* of capture and preview resolutions of same aspect ratio.
*/
cca.views.camera.PhotoResolPreferrer.prototype.pairCapturePreviewResolutions_ =
function(captureResolutions, previewResolutions) {
const toAspectRatio = (w, h) => (w / h).toFixed(4);
const previewRatios = previewResolutions.reduce((rs, [w, h]) => {
const key = toAspectRatio(w, h);
rs[key] = rs[key] || [];
rs[key].push([w, h]);
return rs;
}, {});
const captureRatios = captureResolutions.reduce((rs, [w, h]) => {
const key = toAspectRatio(w, h);
if (key in previewRatios) {
rs[key] = rs[key] || [];
rs[key].push([w, h]);
}
return rs;
}, {});
return Object.entries(captureRatios)
.map(([aspectRatio,
captureRs]) => [captureRs, previewRatios[aspectRatio]]);
};
/**
* @param {string} deviceId
* @param {ResolList} previewResolutions
* @return {Array<[number, number, Array<Object>]>}
* @override
*/
cca.views.camera.PhotoResolPreferrer.prototype.getSortedCandidates = function(
deviceId, previewResolutions) {
const photoResolutions = this.deviceResolutions_[deviceId];
const prefR = this.prefResolution_[deviceId] || {width: 0, height: -1};
return this
.pairCapturePreviewResolutions_(photoResolutions, previewResolutions)
.map(([captureRs, previewRs]) => {
if (captureRs.some(([w, h]) => w == prefR.width && h == prefR.height)) {
var captureR = [prefR.width, prefR.height];
} else {
var captureR = captureRs.reduce(
(captureR, r) => (r[0] > captureR[0] ? r : captureR), [0, -1]);
}
const candidates = previewRs.sort(([w, h], [w2, h2]) => w2 - w)
.map(([width, height]) => ({
audio: false,
video: {
deviceId: {exact: deviceId},
frameRate: {min: 24},
width,
height,
},
}));
// Format of map result:
// [
// [[CaptureW 1, CaptureH 1], [CaptureW 2, CaptureH 2], ...],
// [PreviewConstraint 1, PreviewConstraint 2, ...]
// ]
return [captureR, candidates];
})
.sort(([[w, h]], [[w2, h2]]) => {
if (w == w2 && h == h2) {
return 0;
}
if (w == prefR.width && h == prefR.height) {
return -1;
}
if (w2 == prefR.width && h2 == prefR.height) {
return 1;
}
return w2 * h2 - w * h;
});
};
...@@ -114,29 +114,25 @@ cca.views.ResolutionSettings = function(resolBroker) { ...@@ -114,29 +114,25 @@ cca.views.ResolutionSettings = function(resolBroker) {
'settings-front-photores': () => { 'settings-front-photores': () => {
const element = document.querySelector('#settings-front-photores'); const element = document.querySelector('#settings-front-photores');
if (element.classList.contains('multi-option')) { if (element.classList.contains('multi-option')) {
this.openPhotoResSettings_( this.openPhotoResSettings_(this.frontPhotoSetting_, element);
this.frontSetting_[0], this.frontSetting_[1], element);
} }
}, },
'settings-front-videores': () => { 'settings-front-videores': () => {
const element = document.querySelector('#settings-front-videores'); const element = document.querySelector('#settings-front-videores');
if (element.classList.contains('multi-option')) { if (element.classList.contains('multi-option')) {
this.openVideoResSettings_( this.openVideoResSettings_(this.frontVideoSetting_, element);
this.frontSetting_[0], this.frontSetting_[2], element);
} }
}, },
'settings-back-photores': () => { 'settings-back-photores': () => {
const element = document.querySelector('#settings-back-photores'); const element = document.querySelector('#settings-back-photores');
if (element.classList.contains('multi-option')) { if (element.classList.contains('multi-option')) {
this.openPhotoResSettings_( this.openPhotoResSettings_(this.backPhotoSetting_, element);
this.backSetting_[0], this.backSetting_[1], element);
} }
}, },
'settings-back-videores': () => { 'settings-back-videores': () => {
const element = document.querySelector('#settings-back-videores'); const element = document.querySelector('#settings-back-videores');
if (element.classList.contains('multi-option')) { if (element.classList.contains('multi-option')) {
this.openVideoResSettings_( this.openVideoResSettings_(this.backVideoSetting_, element);
this.backSetting_[0], this.backSetting_[2], element);
} }
}, },
}); });
...@@ -205,34 +201,81 @@ cca.views.ResolutionSettings = function(resolBroker) { ...@@ -205,34 +201,81 @@ cca.views.ResolutionSettings = function(resolBroker) {
document.querySelector('#extcam-resolution-item-template'); document.querySelector('#extcam-resolution-item-template');
/** /**
* Device id and resolutions of front camera. Null for no front camera. * Device id and photo resolutions of front camera. Null if no front camera.
* @type {?DeviceIdResols} * @type {?DeviceIdResols}
* @private * @private
*/ */
this.frontSetting_ = null; this.frontPhotoSetting_ = null;
/** /**
* Device id and resolutions of back camera. Null for no back camera. * Device id and video resolutions of front camera. Null if no front camera.
* @type {?DeviceIdResols} * @type {?DeviceIdResols}
* @private * @private
*/ */
this.backSetting_ = null; this.frontVideoSetting_ = null;
/** /**
* Device id and resolutions of external cameras. * Device id and photo resolutions of back camera. Null if no back camera.
* @type {?DeviceIdResols}
* @private
*/
this.backPhotoSetting_ = null;
/**
* Device id and video resolutions of back camera. Null if no back camera.
* @type {?DeviceIdResols}
* @private
*/
this.backVideoSetting_ = null;
/**
* Device id and photo resolutions of external cameras.
* @type {Array<DeviceIdResols>}
* @private
*/
this.externalPhotoSettings_ = [];
/**
* Device id and video resolutions of external cameras.
* @type {Array<DeviceIdResols>} * @type {Array<DeviceIdResols>}
* @private * @private
*/ */
this.externalSettings_ = []; this.externalVideoSettings_ = [];
/**
* Device id of currently opened resolution setting view.
* @type {?string}
* @private
*/
this.openedSettingDeviceId_ = null;
// End of properties, seal the object. // End of properties, seal the object.
Object.seal(this); Object.seal(this);
this.resolBroker_.addUpdateDeviceResolutionsListener( this.resolBroker_.addPhotoResolChangeListener((front, back, externals) => {
this.updateResolutions.bind(this)); // Filter out resolutions of megapixels < 0.1 i.e. megapixels 0.0
this.resolBroker_.addPhotoResolChangeListener( const zeroMPFilter = (s) => {
s = [...s];
s.splice(3, 1, s[3].filter(([w, h]) => w * h >= 100000));
return s;
};
this.frontPhotoSetting_ = zeroMPFilter(front);
this.backPhotoSetting_ = zeroMPFilter(back);
this.externalPhotoSettings_ = externals.map(zeroMPFilter);
this.updateResolutions_();
});
this.resolBroker_.addVideoResolChangeListener((front, back, externals) => {
this.frontVideoSetting_ = front;
this.backVideoSetting_ = back;
this.externalVideoSettings_ = externals;
this.updateResolutions_();
});
this.resolBroker_.addPhotoPrefResolChangeListener(
this.updateSelectedPhotoResolution_.bind(this)); this.updateSelectedPhotoResolution_.bind(this));
this.resolBroker_.addVideoResolChangeListener( this.resolBroker_.addVideoPrefResolChangeListener(
this.updateSelectedVideoResolution_.bind(this)); this.updateSelectedVideoResolution_.bind(this));
}; };
...@@ -248,7 +291,13 @@ cca.views.ResolutionSettings.prototype = { ...@@ -248,7 +291,13 @@ cca.views.ResolutionSettings.prototype = {
* @private * @private
*/ */
cca.views.ResolutionSettings.prototype.photoOptTextTempl_ = function(w, h) { cca.views.ResolutionSettings.prototype.photoOptTextTempl_ = function(w, h) {
return `${Math.round(w * h / 100000) / 10} megapixels (${w}:${h})`; const gcd = (a, b) => (a <= 0 ? b : gcd(b % a, a));
if (h * w >= 100000) {
return `${Math.round(w * h / 100000) / 10} megapixels (${w}:${h})`;
} else {
const d = gcd(w, h);
return `(${w / d}:${h / d}) ${w} x ${h}px`;
}
}; };
/** /**
...@@ -263,51 +312,68 @@ cca.views.ResolutionSettings.prototype.videoOptTextTempl_ = function(w, h) { ...@@ -263,51 +312,68 @@ cca.views.ResolutionSettings.prototype.videoOptTextTempl_ = function(w, h) {
}; };
/** /**
* Updates resolutions of front, back camera and external cameras. * Finds resolution setting of target device id.
* @param {?DeviceIdResols} frontSetting Resolutions of front camera. * @param {string} deviceId
* @param {?DeviceIdResols} backSetting Resolutions of back camera. * @return {?DeviceIdResols}
* @param {Array<DeviceIdResols>} externalSettings Resolutions of external * @private
* cameras.
*/ */
cca.views.ResolutionSettings.prototype.updateResolutions = function( cca.views.ResolutionSettings.prototype.getDeviceSetting_ = function(deviceId) {
frontSetting, backSetting, externalSettings) { if (this.frontPhotoSetting_ && this.frontPhotoSetting_[0] === deviceId) {
const checkMulti = (item, resolutions, optTextTempl) => { return [this.frontPhotoSetting_, this.frontVideoSetting_];
// Filter out resolutions of megapixels < 0.1 i.e. megapixels 0.0 }
resolutions = resolutions.filter(([w, h]) => w * h >= 100000); if (this.backPhotoSetting_ && this.backPhotoSetting_[0] === deviceId) {
return [this.backPhotoSetting_, this.backVideoSetting_];
}
const idx = this.externalPhotoSettings_.findIndex(([id]) => id === deviceId);
return idx == -1 ?
null :
[this.externalPhotoSettings_[idx], this.externalVideoSettings_[idx]];
};
/**
* Updates resolution information of front, back camera and external cameras.
* @private
*/
cca.views.ResolutionSettings.prototype.updateResolutions_ = function() {
// Since photo/video resolutions may be updated independently and updating
// setting menu requires both information, check if their device ids are
// matched before update.
const compareId = (setting, setting2) =>
(setting && setting[0]) === (setting2 && setting2[0]);
if (!compareId(this.frontPhotoSetting_, this.frontVideoSetting_) ||
!compareId(this.backPhotoItem_, this.backVideoItem_) ||
this.externalPhotoSettings_.length !=
this.externalVideoSettings_.length ||
this.externalPhotoSettings_.some(
(s, index) => !compareId(s, this.externalVideoSettings_[index]))) {
return;
}
const prepItem = (item, [id, w, h, resolutions], optTextTempl) => {
item.dataset.deviceId = id;
item.classList.toggle('multi-option', resolutions.length > 1); item.classList.toggle('multi-option', resolutions.length > 1);
if (resolutions.length == 1) { item.querySelector('.description>span').textContent = optTextTempl(w, h);
const [w, h] = resolutions;
item.dataset.sWidth = w;
item.dataset.sHeight = h;
item.querySelector('.description>span').textContent = optTextTempl(w, h);
}
}; };
// Update front camera setting // Update front camera setting
this.frontSetting_ = frontSetting; cca.state.set('has-front-camera', !!this.frontPhotoSetting_);
cca.state.set('has-front-camera', this.frontSetting_); if (this.frontPhotoSetting_) {
if (this.frontSetting_) { prepItem(
const [deviceId, photoRs, videoRs] = this.frontSetting_; this.frontPhotoItem_, this.frontPhotoSetting_, this.photoOptTextTempl_);
this.frontPhotoItem_.dataset.deviceId = prepItem(
this.frontVideoItem_.dataset.deviceId = deviceId; this.frontVideoItem_, this.frontVideoSetting_, this.videoOptTextTempl_);
checkMulti(this.frontPhotoItem_, photoRs, this.photoOptTextTempl_);
checkMulti(this.frontVideoItem_, videoRs, this.videoOptTextTempl_);
} }
// Update back camera setting // Update back camera setting
this.backSetting_ = backSetting; cca.state.set('has-back-camera', !!this.backPhotoSetting_);
cca.state.set('has-back-camera', this.backSetting_); if (this.backPhotoSetting_) {
if (this.backSetting_) { prepItem(
const [deviceId, photoRs, videoRs] = this.backSetting_; this.backPhotoItem_, this.backPhotoSetting_, this.photoOptTextTempl_);
this.backPhotoItem_.dataset.deviceId = prepItem(
this.backVideoItem_.dataset.deviceId = deviceId; this.backVideoItem_, this.backVideoSetting_, this.videoOptTextTempl_);
checkMulti(this.backPhotoItem_, photoRs, this.photoOptTextTempl_);
checkMulti(this.backVideoItem_, videoRs, this.videoOptTextTempl_);
} }
// Update external camera settings // Update external camera settings
this.externalSettings_ = externalSettings;
// To prevent losing focus on item already exist before update, locate // To prevent losing focus on item already exist before update, locate
// focused item in both previous and current list, pop out all items in // focused item in both previous and current list, pop out all items in
// previous list except those having same deviceId as focused one and // previous list except those having same deviceId as focused one and
...@@ -315,7 +381,8 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function( ...@@ -315,7 +381,8 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function(
const prevFocus = const prevFocus =
this.resMenu_.querySelector('.menu-item.external-camera:focus'); this.resMenu_.querySelector('.menu-item.external-camera:focus');
const prevFId = prevFocus && prevFocus.dataset.deviceId; const prevFId = prevFocus && prevFocus.dataset.deviceId;
const focusIdx = externalSettings.findIndex(([id]) => id === prevFId); const focusIdx =
this.externalPhotoSettings_.findIndex(([id]) => id === prevFId);
const fTitle = const fTitle =
this.resMenu_.querySelector(`.menu-item[data-device-id="${prevFId}"]`); this.resMenu_.querySelector(`.menu-item[data-device-id="${prevFId}"]`);
const focusedId = focusIdx === -1 ? null : prevFId; const focusedId = focusIdx === -1 ? null : prevFId;
...@@ -325,34 +392,45 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function( ...@@ -325,34 +392,45 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function(
(element) => element.dataset.deviceId !== focusedId && (element) => element.dataset.deviceId !== focusedId &&
element.parentNode.removeChild(element)); element.parentNode.removeChild(element));
externalSettings.forEach(([deviceId, photoRs, videoRs], index) => { this.externalPhotoSettings_.forEach((photoSetting, index) => {
if (deviceId === focusedId) { const deviceId = photoSetting[0];
return; const videoSetting = this.externalVideoSettings_[index];
} if (deviceId !== focusedId) {
const extItem = document.importNode(this.extcamItemTempl_.content, true); const extItem = document.importNode(this.extcamItemTempl_.content, true);
const [titleItem, photoItem, videoItem] = var [titleItem, photoItem, videoItem] =
extItem.querySelectorAll('.menu-item'); extItem.querySelectorAll('.menu-item');
titleItem.dataset.deviceId = photoItem.dataset.deviceId =
videoItem.dataset.deviceId = deviceId; photoItem.addEventListener('click', () => {
checkMulti(photoItem, photoRs, this.photoOptTextTempl_); if (photoItem.classList.contains('multi-option')) {
checkMulti(videoItem, videoRs, this.videoOptTextTempl_); this.openPhotoResSettings_(photoSetting, photoItem);
}
photoItem.addEventListener('click', () => { });
if (photoItem.classList.contains('multi-option')) { videoItem.addEventListener('click', () => {
this.openPhotoResSettings_(deviceId, photoRs, photoItem); if (videoItem.classList.contains('multi-option')) {
} this.openVideoResSettings_(videoSetting, videoItem);
}); }
videoItem.addEventListener('click', () => { });
if (videoItem.classList.contains('multi-option')) { if (index < focusIdx) {
this.openVideoResSettings_(deviceId, videoRs, videoItem); this.resMenu_.insertBefore(extItem, fTitle);
} else {
this.resMenu_.appendChild(extItem);
} }
});
if (index < focusIdx) {
this.resMenu_.insertBefore(extItem, fTitle);
} else { } else {
this.resMenu_.appendChild(extItem); titleItem = fTitle;
photoItem = fTitle.nextElementSibling;
videoItem = photoItem.nextElementSibling;
} }
titleItem.dataset.deviceId = deviceId;
prepItem(photoItem, photoSetting, this.photoOptTextTempl_);
prepItem(videoItem, videoSetting, this.videoOptTextTempl_);
}); });
if ((cca.state.get('photoresolutionsettings') ||
cca.state.get('videoresolutionsettings')) &&
!this.getDeviceSetting_(this.openedSettingDeviceId_)) {
cca.nav.close(
cca.state.get('photoresolutionsettings') ? 'photoresolutionsettings' :
'videoresolutionsettings');
}
}; };
/** /**
...@@ -364,15 +442,23 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function( ...@@ -364,15 +442,23 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function(
*/ */
cca.views.ResolutionSettings.prototype.updateSelectedPhotoResolution_ = cca.views.ResolutionSettings.prototype.updateSelectedPhotoResolution_ =
function(deviceId, width, height) { function(deviceId, width, height) {
const [photoItem] = this.resMenu_.querySelectorAll( const [photoSetting] = this.getDeviceSetting_(deviceId);
`.resol-item[data-device-id="${deviceId}"]`); photoSetting[1] = width;
photoItem.dataset.sWidth = width; photoSetting[2] = height;
photoItem.dataset.sHeight = height; if (this.frontPhotoSetting_ && deviceId === this.frontPhotoSetting_[0]) {
var photoItem = this.frontPhotoItem_;
} else if (this.backPhotoSetting_ && deviceId === this.backPhotoSetting_[0]) {
photoItem = this.backPhotoItem_;
} else {
photoItem = this.resMenu_.querySelectorAll(
`.menu-item[data-device-id="${deviceId}"]`)[1];
}
photoItem.querySelector('.description>span').textContent = photoItem.querySelector('.description>span').textContent =
this.photoOptTextTempl_(width, height); this.photoOptTextTempl_(width, height);
// Update setting option if it's opened. // Update setting option if it's opened.
if (cca.state.get('photoresolutionsettings')) { if (cca.state.get('photoresolutionsettings') &&
this.openedSettingDeviceId_ === deviceId) {
this.photoResMenu_ this.photoResMenu_
.querySelector(`input[data-width="${width}"][data-height="${height}"]`) .querySelector(`input[data-width="${width}"][data-height="${height}"]`)
.checked = true; .checked = true;
...@@ -388,15 +474,23 @@ cca.views.ResolutionSettings.prototype.updateSelectedPhotoResolution_ = ...@@ -388,15 +474,23 @@ cca.views.ResolutionSettings.prototype.updateSelectedPhotoResolution_ =
*/ */
cca.views.ResolutionSettings.prototype.updateSelectedVideoResolution_ = cca.views.ResolutionSettings.prototype.updateSelectedVideoResolution_ =
function(deviceId, width, height) { function(deviceId, width, height) {
const [, videoItem] = this.resMenu_.querySelectorAll( const [, videoSetting] = this.getDeviceSetting_(deviceId);
`.resol-item[data-device-id="${deviceId}"]`); videoSetting[1] = width;
videoItem.dataset.sWidth = width; videoSetting[2] = height;
videoItem.dataset.sHeight = height; if (this.frontVideoSetting_ && deviceId === this.frontVideoSetting_[0]) {
var videoItem = this.frontVideoItem_;
} else if (this.backVideoSetting_ && deviceId === this.backVideoSetting_[0]) {
videoItem = this.backVideoItem_;
} else {
videoItem = this.resMenu_.querySelectorAll(
`.menu-item[data-device-id="${deviceId}"]`)[2];
}
videoItem.querySelector('.description>span').textContent = videoItem.querySelector('.description>span').textContent =
this.videoOptTextTempl_(width, height); this.videoOptTextTempl_(width, height);
// Update setting option if it's opened. // Update setting option if it's opened.
if (cca.state.get('videoresolutionsettings')) { if (cca.state.get('videoresolutionsettings') &&
this.openedSettingDeviceId_ === deviceId) {
this.videoResMenu_ this.videoResMenu_
.querySelector(`input[data-width="${width}"][data-height="${height}"]`) .querySelector(`input[data-width="${width}"][data-height="${height}"]`)
.checked = true; .checked = true;
...@@ -405,37 +499,37 @@ cca.views.ResolutionSettings.prototype.updateSelectedVideoResolution_ = ...@@ -405,37 +499,37 @@ cca.views.ResolutionSettings.prototype.updateSelectedVideoResolution_ =
/** /**
* Opens photo resolution setting view. * Opens photo resolution setting view.
* @param {string} deviceId Device id of the opened view. * @param {DeviceIdResols} setting Photo resolution setting.
* @param {ResolList} resolutions Resolutions to be shown in opened view. * @param {HTMLElement} resolItem Dom element from upper layer menu item showing
* @param {HTMLElement} resolItem Dom element from upper layer holding the * title of the selected resolution.
* selected resolution width, height.
* @private * @private
*/ */
cca.views.ResolutionSettings.prototype.openPhotoResSettings_ = function( cca.views.ResolutionSettings.prototype.openPhotoResSettings_ = function(
deviceId, resolutions, resolItem) { setting, resolItem) {
const [deviceId, width, height, resolutions] = setting;
this.openedSettingDeviceId_ = deviceId;
this.updateMenu_( this.updateMenu_(
resolItem, this.photoResMenu_, this.photoOptTextTempl_, resolItem, this.photoResMenu_, this.photoOptTextTempl_,
(w, h) => this.resolBroker_.requestChangePhotoResol(deviceId, w, h), (w, h) => this.resolBroker_.requestChangePhotoPrefResol(deviceId, w, h),
resolutions, parseInt(resolItem.dataset.sWidth), resolutions, width, height);
parseInt(resolItem.dataset.sHeight));
this.openSubSettings('photoresolutionsettings'); this.openSubSettings('photoresolutionsettings');
}; };
/** /**
* Opens video resolution setting view. * Opens video resolution setting view.
* @param {string} deviceId Device id of the opened view. * @param {DeviceIdResols} setting Video resolution setting.
* @param {ResolList} resolutions Resolutions to be shown in opened view. * @param {HTMLElement} resolItem Dom element from upper layer menu item showing
* @param {HTMLElement} resolItem Dom element from upper layer holding the * title of the selected resolution.
* selected resolution width, height.
* @private * @private
*/ */
cca.views.ResolutionSettings.prototype.openVideoResSettings_ = function( cca.views.ResolutionSettings.prototype.openVideoResSettings_ = function(
deviceId, resolutions, resolItem) { setting, resolItem) {
const [deviceId, width, height, resolutions] = setting;
this.openedSettingDeviceId_ = deviceId;
this.updateMenu_( this.updateMenu_(
resolItem, this.videoResMenu_, this.videoOptTextTempl_, resolItem, this.videoResMenu_, this.videoOptTextTempl_,
(w, h) => this.resolBroker_.requestChangeVideoResol(deviceId, w, h), (w, h) => this.resolBroker_.requestChangeVideoPrefResol(deviceId, w, h),
resolutions, parseInt(resolItem.dataset.sWidth), resolutions, width, height);
parseInt(resolItem.dataset.sHeight));
this.openSubSettings('videoresolutionsettings'); this.openSubSettings('videoresolutionsettings');
}; };
...@@ -458,11 +552,6 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function( ...@@ -458,11 +552,6 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function(
resolItem, menu, optTextTempl, onChange, resolutions, selectedWidth, resolItem, menu, optTextTempl, onChange, resolutions, selectedWidth,
selectedHeight) { selectedHeight) {
const captionText = resolItem.querySelector('.description>span'); const captionText = resolItem.querySelector('.description>span');
const updateSelection = (w, h) => {
resolItem.dataset.sWidth = w;
resolItem.dataset.sHeight = h;
captionText.textContent = optTextTempl(w, h);
};
captionText.textContent = ''; captionText.textContent = '';
menu.querySelectorAll('.menu-item') menu.querySelectorAll('.menu-item')
.forEach((element) => element.parentNode.removeChild(element)); .forEach((element) => element.parentNode.removeChild(element));
...@@ -476,7 +565,7 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function( ...@@ -476,7 +565,7 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function(
inputElement.dataset.width = w; inputElement.dataset.width = w;
inputElement.dataset.height = h; inputElement.dataset.height = h;
if (w == selectedWidth && h == selectedHeight) { if (w == selectedWidth && h == selectedHeight) {
updateSelection(w, h); captionText.textContent = optTextTempl(w, h);
inputElement.checked = true; inputElement.checked = true;
} }
inputElement.addEventListener('click', (event) => { inputElement.addEventListener('click', (event) => {
...@@ -486,7 +575,7 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function( ...@@ -486,7 +575,7 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function(
}); });
inputElement.addEventListener('change', (event) => { inputElement.addEventListener('change', (event) => {
if (inputElement.checked) { if (inputElement.checked) {
updateSelection(w, h); captionText.textContent = optTextTempl(w, h);
onChange(w, h); onChange(w, h);
} }
}); });
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
<script src="../js/views/camera/options.js"></script> <script src="../js/views/camera/options.js"></script>
<script src="../js/views/camera/preview.js"></script> <script src="../js/views/camera/preview.js"></script>
<script src="../js/views/camera/recordtime.js"></script> <script src="../js/views/camera/recordtime.js"></script>
<script src="../js/views/camera/resolution_preference.js"></script>
<script src="../js/views/camera/timertick.js"></script> <script src="../js/views/camera/timertick.js"></script>
<script src="../js/views/camera/modes.js"></script> <script src="../js/views/camera/modes.js"></script>
<script src="../js/views/dialog.js"></script> <script src="../js/views/dialog.js"></script>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment