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") {
"src/js/views/camera/options.js",
"src/js/views/camera/preview.js",
"src/js/views/camera/recordtime.js",
"src/js/views/camera/resolution_preference.js",
"src/js/views/camera/timertick.js",
]
......
......@@ -137,6 +137,7 @@ RESOURCES = \
src/js/views/camera/options.js \
src/js/views/camera/preview.js \
src/js/views/camera/recordtime.js \
src/js/views/camera/resolution_preference.js \
src/js/views/camera/timertick.js \
src/js/views/dialog.js \
src/js/views/gallery_base.js \
......
......@@ -15,9 +15,9 @@ var cca = cca || {};
*/
/**
* A list of device id and the supported video, photo resolutions of its
* corresponding video device.
* @typedef {[number, ResolList, ResolList]} DeviceIdResols
* Tuple of device id, width, height of preferred capture resolution and all
* available resolutions for a specific video device.
* @typedef {[string, number, number, ResolList]} DeviceIdResols
*/
/**
......@@ -31,7 +31,7 @@ cca.ResolutionEventBroker = function() {
* @type {function(string, number, number)}
* @private
*/
this.photoChangeResolHandler_ = () => {};
this.photoChangePrefResolHandler_ = () => {};
/**
* Handler for requests of changing user-preferred resolution used in video
......@@ -39,29 +39,39 @@ cca.ResolutionEventBroker = function() {
* @type {function(string, number, number)}
* @private
*/
this.videoChangeResolHandler_ = () => {};
this.videoChangePrefResolHandler_ = () => {};
/**
* Listener for changes of resolution currently used in photo taking.
* @type {function(string, number, number)}
* Listener for changes of device ids of currently available front, back and
* external cameras and all of their supported photo resolutions.
* @type {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* @private
*/
this.photoResolChangeListener_ = () => {};
/**
* Listener for changes of resolution currently used in video recording.
* @type {function(string, number, number)}
* Listener for changes of device ids of currently available front, back and
* external cameras and all of their supported video resolutions.
* @type {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* @private
*/
this.videoResolChangeListener_ = () => {};
/**
* Listener for changes of currently available device-resolutions of front,
* back, external cameras.
* @type {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* Listener for changes of preferred photo resolution used on particular video
* device.
* @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
*/
this.deviceResolListener_ = () => {};
this.videoPrefResolChangeListener_ = () => {};
};
/**
......@@ -70,9 +80,9 @@ cca.ResolutionEventBroker = function() {
* @param {function(string, number, number)} handler Called with device id of
* video device to be changed and width, height of new resolution.
*/
cca.ResolutionEventBroker.prototype.registerChangePhotoResolHandler = function(
handler) {
this.photoChangeResolHandler_ = handler;
cca.ResolutionEventBroker.prototype.registerChangePhotoPrefResolHandler =
function(handler) {
this.photoChangePrefResolHandler_ = handler;
};
/**
......@@ -81,9 +91,9 @@ cca.ResolutionEventBroker.prototype.registerChangePhotoResolHandler = function(
* @param {function(string, number, number)} handler Called with device id of
* video device to be changed and width, height of new resolution.
*/
cca.ResolutionEventBroker.prototype.registerChangeVideoResolHandler = function(
handler) {
this.videoChangeResolHandler_ = handler;
cca.ResolutionEventBroker.prototype.registerChangeVideoPrefResolHandler =
function(handler) {
this.videoChangePrefResolHandler_ = handler;
};
/**
......@@ -92,9 +102,9 @@ cca.ResolutionEventBroker.prototype.registerChangeVideoResolHandler = function(
* @param {number} width Change to resolution width.
* @param {number} height Change to resolution height.
*/
cca.ResolutionEventBroker.prototype.requestChangePhotoResol = function(
cca.ResolutionEventBroker.prototype.requestChangePhotoPrefResol = function(
deviceId, width, height) {
this.photoChangeResolHandler_(deviceId, width, height);
this.photoChangePrefResolHandler_(deviceId, width, height);
};
/**
......@@ -103,15 +113,16 @@ cca.ResolutionEventBroker.prototype.requestChangePhotoResol = function(
* @param {number} width Change to resolution width.
* @param {number} height Change to resolution height.
*/
cca.ResolutionEventBroker.prototype.requestChangeVideoResol = function(
cca.ResolutionEventBroker.prototype.requestChangeVideoPrefResol = function(
deviceId, width, height) {
this.videoChangeResolHandler_(deviceId, width, height);
this.videoChangePrefResolHandler_(deviceId, width, height);
};
/**
Adds listener for changes of resolution currently used in photo taking.
* @param {function(string, number, number)} listener Called with changed device
* id and new resolution width, height.
* Adds listener for changes of available photo resolutions of all cameras.
* @param {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* listener Called with device id, preferred photo capture resolution and
* available photo resolutions of front, back and external cameras.
*/
cca.ResolutionEventBroker.prototype.addPhotoResolChangeListener = function(
listener) {
......@@ -119,9 +130,10 @@ cca.ResolutionEventBroker.prototype.addPhotoResolChangeListener = function(
};
/**
* Adds listener for changes of resolution currently used in video recording.
* @param {function(string, number, number)} listener Called with changed device
* id and new resolution width, height.
* Adds listener for changes of available video resolutions of all cameras.
* @param {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* listener Called with device id, preferred video capture resolution and
* available video resolutions of front, back and external cameras.
*/
cca.ResolutionEventBroker.prototype.addVideoResolChangeListener = function(
listener) {
......@@ -129,47 +141,77 @@ cca.ResolutionEventBroker.prototype.addVideoResolChangeListener = function(
};
/**
* Notifies the change of resolution currently used in photo taking.
* @param {string} deviceId Device id of changed video device.
* @param {number} width New resolution width.
* @param {number} height New resolution height.
* Notifies the change of available photo resolution of all cameras.
* @param {?DeviceIdResols} frontResolutions Device id of front camera and
* all of its available supported photo resolutions.
* @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(
deviceId, width, height) {
this.photoResolChangeListener_(deviceId, width, height);
frontResolutions, backResolutions, externalResolutions) {
this.photoResolChangeListener_(
frontResolutions, backResolutions, externalResolutions);
};
/**
* Notifies the change of resolution currently used in video recording.
* @param {string} deviceId Device id of changed video device.
* @param {number} width New resolution width.
* @param {number} height New resolution height.
* Notifies the change of available video resolution of all cameras.
* @param {?DeviceIdResols} frontResolutions Device id of front camera and
* all of its available supported video resolutions.
* @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(
deviceId, width, height) {
this.videoResolChangeListener_(deviceId, width, height);
frontResolutions, backResolutions, externalResolutions) {
this.videoResolChangeListener_(
frontResolutions, backResolutions, externalResolutions);
};
/**
* Adds listener for changes of currently available device-resolutions of
* all cameras.
* @param {function(?DeviceIdResols, ?DeviceIdResols, Array<DeviceIdResols>)}
* listener Listener function called with deviceId-resolutions of front,
* back and external cameras.
* Adds listener for changes of preferred resolution used in taking photo 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.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 =
function(listener) {
this.deviceResolListener_ = listener;
cca.ResolutionEventBroker.prototype.notifyPhotoPrefResolChange = function(
deviceId, width, height) {
this.photoPrefResolChangeListener_(deviceId, width, height);
};
/**
* Notifies the change of currently available device-resolutions of all cameras.
* @param {?DeviceIdResols} front DeviceId-resolutions of front camera.
* @param {?DeviceIdResols} back DeviceId-resolutions of back camera.
* @param {Array<DeviceIdResols>} externals DeviceId-resolutions of external
* cameras.
* Notifies the change of preferred resolution used in video recording 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.notifyUpdateDeviceResolutions = function(
front, back, externals) {
this.deviceResolListener_(front, back, externals);
cca.ResolutionEventBroker.prototype.notifyVideoPrefResolChange = function(
deviceId, width, height) {
this.videoPrefResolChangeListener_(deviceId, width, height);
};
......@@ -30,6 +30,20 @@ cca.views.Camera = function(model, resolBroker) {
*/
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.
* @type {cca.views.camera.Layout}
......@@ -49,8 +63,9 @@ cca.views.Camera = function(model, resolBroker) {
* @type {cca.views.camera.Options}
* @private
*/
this.options_ =
new cca.views.camera.Options(resolBroker, this.stop_.bind(this));
this.options_ = new cca.views.camera.Options(
this.photoResolPreferrer_, this.videoResolPreferrer_,
this.stop_.bind(this));
/**
* Modes for the camera.
......@@ -58,8 +73,8 @@ cca.views.Camera = function(model, resolBroker) {
* @private
*/
this.modes_ = new cca.views.camera.Modes(
resolBroker, this.stop_.bind(this), this.stop_.bind(this),
async (blob, isMotionPicture, filename) => {
this.photoResolPreferrer_, this.videoResolPreferrer_,
this.stop_.bind(this), async (blob, isMotionPicture, filename) => {
if (blob) {
cca.metrics.log(
cca.metrics.Type.CAPTURE, this.facingMode_, blob.mins);
......@@ -225,10 +240,9 @@ cca.views.Camera.prototype.stop_ = function() {
cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) {
let supportedModes = null;
for (const mode of this.modes_.getModeCandidates()) {
const [photoRs, videoRs] =
await this.options_.getDeviceResolutions(deviceId);
const previewRs = (await this.options_.getDeviceResolutions(deviceId))[1];
for (const [[width, height], previewCandidates] of this.modes_
.getResolutionCandidates(mode, deviceId, photoRs, videoRs)) {
.getResolutionCandidates(mode, deviceId, previewRs)) {
for (const constraints of previewCandidates) {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
......
......@@ -21,16 +21,15 @@ cca.views.camera = cca.views.camera || {};
/**
* Mode controller managing capture sequence of different camera mode.
* @param {cca.ResolutionEventBroker} resolBroker
* @param {function()} doSwitchResolution Callback to trigger resolution
* switching.
* @param {cca.views.camera.PhotoResolPreferrer} photoResolPreferrer
* @param {cca.views.camera.VideoResolPreferrer} videoResolPreferrer
* @param {function()} doSwitchMode Callback to trigger mode switching.
* @param {function(?Blob, boolean, string): Promise} doSavePicture Callback for
* saving picture.
* @constructor
*/
cca.views.camera.Modes = function(
resolBroker, doSwitchResolution, doSwitchMode, doSavePicture) {
photoResolPreferrer, videoResolPreferrer, doSwitchMode, doSavePicture) {
/**
* @type {function()}
* @private
......@@ -63,20 +62,6 @@ cca.views.camera.Modes = function(
*/
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.
* @type {number}
......@@ -101,7 +86,7 @@ cca.views.camera.Modes = function(
captureFactory: () =>
new cca.views.camera.Video(this.stream_, this.doSavePicture_),
isSupported: async () => true,
resolutionConfig: this.videoResConfig_,
resolutionConfig: videoResolPreferrer,
nextMode: 'photo-mode',
},
'photo-mode': {
......@@ -109,7 +94,7 @@ cca.views.camera.Modes = function(
this.stream_, this.doSavePicture_, this.captureWidth_,
this.captureHeight_),
isSupported: async () => true,
resolutionConfig: this.photoResConfig_,
resolutionConfig: photoResolPreferrer,
nextMode: 'square-mode',
},
'square-mode': {
......@@ -117,7 +102,7 @@ cca.views.camera.Modes = function(
this.stream_, this.doSavePicture_, this.captureWidth_,
this.captureHeight_),
isSupported: async () => true,
resolutionConfig: this.photoResConfig_,
resolutionConfig: photoResolPreferrer,
nextMode: 'portrait-mode',
},
'portrait-mode': {
......@@ -139,7 +124,7 @@ cca.views.camera.Modes = function(
return false;
}
},
resolutionConfig: this.photoResConfig_,
resolutionConfig: photoResolPreferrer,
nextMode: 'video-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.
* @param {string} mode Class name of the switching mode.
......@@ -500,15 +204,14 @@ cca.views.camera.Modes.prototype.getModeCandidates = function() {
* constraints for the given mode.
* @param {string} mode
* @param {string} deviceId
* @param {ResolList} photoResolutions
* @param {ResolList} videoResolutions
* @param {ResolList} previewResolutions
* @return {Array<[number, number, Array<Object>]>} Result capture resolution
* width, height and constraints-candidates for its preview.
*/
cca.views.camera.Modes.prototype.getResolutionCandidates = function(
mode, deviceId, photoResolutions, videoResolutions) {
mode, deviceId, previewResolutions) {
return this.allModes_[mode].resolutionConfig.getSortedCandidates(
deviceId, photoResolutions, videoResolutions);
deviceId, previewResolutions);
};
/**
......@@ -559,7 +262,7 @@ cca.views.camera.Modes.prototype.updateMode =
this.captureWidth_ = captureWidth;
this.captureHeight_ = captureHeight;
this.current = this.allModes_[mode].captureFactory();
this.allModes_[mode].resolutionConfig.updateSelectedResolution(
this.allModes_[mode].resolutionConfig.updateCurrentResolution(
deviceId, captureWidth, captureHeight);
};
......
......@@ -21,16 +21,24 @@ cca.views.camera = cca.views.camera || {};
/**
* 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.
* @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
*/
this.resolBroker_ = resolBroker;
this.photoResolPreferrer_ = photoResolPreferrer;
/**
* @type {cca.views.camera.VideoResolPreferrer}
* @private
*/
this.videoResolPreferrer_ = videoResolPreferrer;
/**
* @type {function()}
......@@ -132,13 +140,6 @@ cca.views.camera.Options.FRONT_CAMERA_LABEL = 'Front 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.
* @private
......@@ -300,15 +301,19 @@ cca.views.camera.Options.prototype.maybeRefreshVideoDeviceIds_ = function() {
case cca.views.camera.Options.BACK_CAMERA_LABEL:
backSetting = setting;
break;
case cca.views.camera.Options.EXTERNAL_CAMERA_LABEL:
externalSettings.push(setting);
break;
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(
frontSetting, backSetting, externalSettings);
this.photoResolPreferrer_.updateResolutions(
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) {
'settings-front-photores': () => {
const element = document.querySelector('#settings-front-photores');
if (element.classList.contains('multi-option')) {
this.openPhotoResSettings_(
this.frontSetting_[0], this.frontSetting_[1], element);
this.openPhotoResSettings_(this.frontPhotoSetting_, element);
}
},
'settings-front-videores': () => {
const element = document.querySelector('#settings-front-videores');
if (element.classList.contains('multi-option')) {
this.openVideoResSettings_(
this.frontSetting_[0], this.frontSetting_[2], element);
this.openVideoResSettings_(this.frontVideoSetting_, element);
}
},
'settings-back-photores': () => {
const element = document.querySelector('#settings-back-photores');
if (element.classList.contains('multi-option')) {
this.openPhotoResSettings_(
this.backSetting_[0], this.backSetting_[1], element);
this.openPhotoResSettings_(this.backPhotoSetting_, element);
}
},
'settings-back-videores': () => {
const element = document.querySelector('#settings-back-videores');
if (element.classList.contains('multi-option')) {
this.openVideoResSettings_(
this.backSetting_[0], this.backSetting_[2], element);
this.openVideoResSettings_(this.backVideoSetting_, element);
}
},
});
......@@ -205,34 +201,81 @@ cca.views.ResolutionSettings = function(resolBroker) {
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}
* @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}
* @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>}
* @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.
Object.seal(this);
this.resolBroker_.addUpdateDeviceResolutionsListener(
this.updateResolutions.bind(this));
this.resolBroker_.addPhotoResolChangeListener(
this.resolBroker_.addPhotoResolChangeListener((front, back, externals) => {
// Filter out resolutions of megapixels < 0.1 i.e. megapixels 0.0
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.resolBroker_.addVideoResolChangeListener(
this.resolBroker_.addVideoPrefResolChangeListener(
this.updateSelectedVideoResolution_.bind(this));
};
......@@ -248,7 +291,13 @@ cca.views.ResolutionSettings.prototype = {
* @private
*/
cca.views.ResolutionSettings.prototype.photoOptTextTempl_ = function(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) {
};
/**
* Updates resolutions of front, back camera and external cameras.
* @param {?DeviceIdResols} frontSetting Resolutions of front camera.
* @param {?DeviceIdResols} backSetting Resolutions of back camera.
* @param {Array<DeviceIdResols>} externalSettings Resolutions of external
* cameras.
*/
cca.views.ResolutionSettings.prototype.updateResolutions = function(
frontSetting, backSetting, externalSettings) {
const checkMulti = (item, resolutions, optTextTempl) => {
// Filter out resolutions of megapixels < 0.1 i.e. megapixels 0.0
resolutions = resolutions.filter(([w, h]) => w * h >= 100000);
* Finds resolution setting of target device id.
* @param {string} deviceId
* @return {?DeviceIdResols}
* @private
*/
cca.views.ResolutionSettings.prototype.getDeviceSetting_ = function(deviceId) {
if (this.frontPhotoSetting_ && this.frontPhotoSetting_[0] === deviceId) {
return [this.frontPhotoSetting_, this.frontVideoSetting_];
}
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);
if (resolutions.length == 1) {
const [w, h] = resolutions;
item.dataset.sWidth = w;
item.dataset.sHeight = h;
item.querySelector('.description>span').textContent = optTextTempl(w, h);
}
};
// Update front camera setting
this.frontSetting_ = frontSetting;
cca.state.set('has-front-camera', this.frontSetting_);
if (this.frontSetting_) {
const [deviceId, photoRs, videoRs] = this.frontSetting_;
this.frontPhotoItem_.dataset.deviceId =
this.frontVideoItem_.dataset.deviceId = deviceId;
checkMulti(this.frontPhotoItem_, photoRs, this.photoOptTextTempl_);
checkMulti(this.frontVideoItem_, videoRs, this.videoOptTextTempl_);
cca.state.set('has-front-camera', !!this.frontPhotoSetting_);
if (this.frontPhotoSetting_) {
prepItem(
this.frontPhotoItem_, this.frontPhotoSetting_, this.photoOptTextTempl_);
prepItem(
this.frontVideoItem_, this.frontVideoSetting_, this.videoOptTextTempl_);
}
// Update back camera setting
this.backSetting_ = backSetting;
cca.state.set('has-back-camera', this.backSetting_);
if (this.backSetting_) {
const [deviceId, photoRs, videoRs] = this.backSetting_;
this.backPhotoItem_.dataset.deviceId =
this.backVideoItem_.dataset.deviceId = deviceId;
checkMulti(this.backPhotoItem_, photoRs, this.photoOptTextTempl_);
checkMulti(this.backVideoItem_, videoRs, this.videoOptTextTempl_);
cca.state.set('has-back-camera', !!this.backPhotoSetting_);
if (this.backPhotoSetting_) {
prepItem(
this.backPhotoItem_, this.backPhotoSetting_, this.photoOptTextTempl_);
prepItem(
this.backVideoItem_, this.backVideoSetting_, this.videoOptTextTempl_);
}
// Update external camera settings
this.externalSettings_ = externalSettings;
// To prevent losing focus on item already exist before update, locate
// focused item in both previous and current list, pop out all items in
// previous list except those having same deviceId as focused one and
......@@ -315,7 +381,8 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function(
const prevFocus =
this.resMenu_.querySelector('.menu-item.external-camera:focus');
const prevFId = prevFocus && prevFocus.dataset.deviceId;
const focusIdx = externalSettings.findIndex(([id]) => id === prevFId);
const focusIdx =
this.externalPhotoSettings_.findIndex(([id]) => id === prevFId);
const fTitle =
this.resMenu_.querySelector(`.menu-item[data-device-id="${prevFId}"]`);
const focusedId = focusIdx === -1 ? null : prevFId;
......@@ -325,26 +392,22 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function(
(element) => element.dataset.deviceId !== focusedId &&
element.parentNode.removeChild(element));
externalSettings.forEach(([deviceId, photoRs, videoRs], index) => {
if (deviceId === focusedId) {
return;
}
this.externalPhotoSettings_.forEach((photoSetting, index) => {
const deviceId = photoSetting[0];
const videoSetting = this.externalVideoSettings_[index];
if (deviceId !== focusedId) {
const extItem = document.importNode(this.extcamItemTempl_.content, true);
const [titleItem, photoItem, videoItem] =
var [titleItem, photoItem, videoItem] =
extItem.querySelectorAll('.menu-item');
titleItem.dataset.deviceId = photoItem.dataset.deviceId =
videoItem.dataset.deviceId = deviceId;
checkMulti(photoItem, photoRs, this.photoOptTextTempl_);
checkMulti(videoItem, videoRs, this.videoOptTextTempl_);
photoItem.addEventListener('click', () => {
if (photoItem.classList.contains('multi-option')) {
this.openPhotoResSettings_(deviceId, photoRs, photoItem);
this.openPhotoResSettings_(photoSetting, photoItem);
}
});
videoItem.addEventListener('click', () => {
if (videoItem.classList.contains('multi-option')) {
this.openVideoResSettings_(deviceId, videoRs, videoItem);
this.openVideoResSettings_(videoSetting, videoItem);
}
});
if (index < focusIdx) {
......@@ -352,7 +415,22 @@ cca.views.ResolutionSettings.prototype.updateResolutions = function(
} else {
this.resMenu_.appendChild(extItem);
}
} else {
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(
*/
cca.views.ResolutionSettings.prototype.updateSelectedPhotoResolution_ =
function(deviceId, width, height) {
const [photoItem] = this.resMenu_.querySelectorAll(
`.resol-item[data-device-id="${deviceId}"]`);
photoItem.dataset.sWidth = width;
photoItem.dataset.sHeight = height;
const [photoSetting] = this.getDeviceSetting_(deviceId);
photoSetting[1] = width;
photoSetting[2] = 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 =
this.photoOptTextTempl_(width, height);
// Update setting option if it's opened.
if (cca.state.get('photoresolutionsettings')) {
if (cca.state.get('photoresolutionsettings') &&
this.openedSettingDeviceId_ === deviceId) {
this.photoResMenu_
.querySelector(`input[data-width="${width}"][data-height="${height}"]`)
.checked = true;
......@@ -388,15 +474,23 @@ cca.views.ResolutionSettings.prototype.updateSelectedPhotoResolution_ =
*/
cca.views.ResolutionSettings.prototype.updateSelectedVideoResolution_ =
function(deviceId, width, height) {
const [, videoItem] = this.resMenu_.querySelectorAll(
`.resol-item[data-device-id="${deviceId}"]`);
videoItem.dataset.sWidth = width;
videoItem.dataset.sHeight = height;
const [, videoSetting] = this.getDeviceSetting_(deviceId);
videoSetting[1] = width;
videoSetting[2] = 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 =
this.videoOptTextTempl_(width, height);
// Update setting option if it's opened.
if (cca.state.get('videoresolutionsettings')) {
if (cca.state.get('videoresolutionsettings') &&
this.openedSettingDeviceId_ === deviceId) {
this.videoResMenu_
.querySelector(`input[data-width="${width}"][data-height="${height}"]`)
.checked = true;
......@@ -405,37 +499,37 @@ cca.views.ResolutionSettings.prototype.updateSelectedVideoResolution_ =
/**
* Opens photo resolution setting view.
* @param {string} deviceId Device id of the opened view.
* @param {ResolList} resolutions Resolutions to be shown in opened view.
* @param {HTMLElement} resolItem Dom element from upper layer holding the
* selected resolution width, height.
* @param {DeviceIdResols} setting Photo resolution setting.
* @param {HTMLElement} resolItem Dom element from upper layer menu item showing
* title of the selected resolution.
* @private
*/
cca.views.ResolutionSettings.prototype.openPhotoResSettings_ = function(
deviceId, resolutions, resolItem) {
setting, resolItem) {
const [deviceId, width, height, resolutions] = setting;
this.openedSettingDeviceId_ = deviceId;
this.updateMenu_(
resolItem, this.photoResMenu_, this.photoOptTextTempl_,
(w, h) => this.resolBroker_.requestChangePhotoResol(deviceId, w, h),
resolutions, parseInt(resolItem.dataset.sWidth),
parseInt(resolItem.dataset.sHeight));
(w, h) => this.resolBroker_.requestChangePhotoPrefResol(deviceId, w, h),
resolutions, width, height);
this.openSubSettings('photoresolutionsettings');
};
/**
* Opens video resolution setting view.
* @param {string} deviceId Device id of the opened view.
* @param {ResolList} resolutions Resolutions to be shown in opened view.
* @param {HTMLElement} resolItem Dom element from upper layer holding the
* selected resolution width, height.
* @param {DeviceIdResols} setting Video resolution setting.
* @param {HTMLElement} resolItem Dom element from upper layer menu item showing
* title of the selected resolution.
* @private
*/
cca.views.ResolutionSettings.prototype.openVideoResSettings_ = function(
deviceId, resolutions, resolItem) {
setting, resolItem) {
const [deviceId, width, height, resolutions] = setting;
this.openedSettingDeviceId_ = deviceId;
this.updateMenu_(
resolItem, this.videoResMenu_, this.videoOptTextTempl_,
(w, h) => this.resolBroker_.requestChangeVideoResol(deviceId, w, h),
resolutions, parseInt(resolItem.dataset.sWidth),
parseInt(resolItem.dataset.sHeight));
(w, h) => this.resolBroker_.requestChangeVideoPrefResol(deviceId, w, h),
resolutions, width, height);
this.openSubSettings('videoresolutionsettings');
};
......@@ -458,11 +552,6 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function(
resolItem, menu, optTextTempl, onChange, resolutions, selectedWidth,
selectedHeight) {
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 = '';
menu.querySelectorAll('.menu-item')
.forEach((element) => element.parentNode.removeChild(element));
......@@ -476,7 +565,7 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function(
inputElement.dataset.width = w;
inputElement.dataset.height = h;
if (w == selectedWidth && h == selectedHeight) {
updateSelection(w, h);
captionText.textContent = optTextTempl(w, h);
inputElement.checked = true;
}
inputElement.addEventListener('click', (event) => {
......@@ -486,7 +575,7 @@ cca.views.ResolutionSettings.prototype.updateMenu_ = function(
});
inputElement.addEventListener('change', (event) => {
if (inputElement.checked) {
updateSelection(w, h);
captionText.textContent = optTextTempl(w, h);
onChange(w, h);
}
});
......
......@@ -35,6 +35,7 @@
<script src="../js/views/camera/options.js"></script>
<script src="../js/views/camera/preview.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/modes.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