Skip to content

Commit

Permalink
Support background filter for iOS 16+
Browse files Browse the repository at this point in the history
  • Loading branch information
ltrung committed Nov 2, 2022
1 parent e2ffd23 commit 7f69440
Show file tree
Hide file tree
Showing 13 changed files with 73 additions and 46 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add the audio output gain and frequency to the meeting readiness checker's configuration. The readiness checker uses this value to set the "Play Tone" gain and frequency.
- Add support for background filter starting from iOS 16.

### Removed

Expand Down
56 changes: 28 additions & 28 deletions docs/classes/defaultbrowserbehavior.html

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions docs/modules/backgroundfilter_video_processor.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ <h2>Can I use a background filter in my application?</h2>
<a href="#browser-compatibility" id="browser-compatibility" style="color: inherit; text-decoration: none;">
<h3>Browser compatibility</h3>
</a>
<p>A background filter in the Amazon Chime SDK for JavaScript works in Firefox, Chrome, and Chromium-based browsers (including Electron) on desktop, Android, and Safari 14.1 and later on macOS.</p>
<p>There is a known issue with <code>VideoFrameProcessor</code> in Safari 15: see <a href="https://github.com/aws/amazon-chime-sdk-js/issues/1059">github issue 1059</a></p>
<p>A background filter in the Amazon Chime SDK for JavaScript works in Firefox, Chrome, and Chromium-based browsers
(including Electron) on desktop, Android, and Safari 14.1 and later.</p>
<p>Note that there is a known issue with <code>VideoFrameProcessor</code> in Safari 15: see <a href="https://github.com/aws/amazon-chime-sdk-js/issues/1059">github issue 1059</a>. This has been fixed with Safari 16.</p>
<p>A background filter can be CPU-intensive and the web runtime affects performance. As such, not all mobile devices or lower-spec laptop or desktop computers will be sufficiently powerful, or will be able to use a background filter while also supporting multiple video streams and rich application functionality.</p>
<p>See the sections “<a href="#checking-for-support-before-offering-a-background-filter">Checking for support before offering a background filter</a>” for more details about checking for support.</p>
<a href="#simd-support" id="simd-support" style="color: inherit; text-decoration: none;">
Expand Down
5 changes: 3 additions & 2 deletions guides/15_Background_Filter_Video_Processor.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ Background replacement is available as part of the Amazon Chime SDK for JavaScri

### Browser compatibility

A background filter in the Amazon Chime SDK for JavaScript works in Firefox, Chrome, and Chromium-based browsers (including Electron) on desktop, Android, and Safari 14.1 and later on macOS.
A background filter in the Amazon Chime SDK for JavaScript works in Firefox, Chrome, and Chromium-based browsers
(including Electron) on desktop, Android, and Safari 14.1 and later.

There is a known issue with `VideoFrameProcessor` in Safari 15: see [github issue 1059](https://github.com/aws/amazon-chime-sdk-js/issues/1059)
Note that there is a known issue with `VideoFrameProcessor` in Safari 15: see [github issue 1059](https://github.com/aws/amazon-chime-sdk-js/issues/1059). This has been fixed with Safari 16.

A background filter can be CPU-intensive and the web runtime affects performance. As such, not all mobile devices or lower-spec laptop or desktop computers will be sufficiently powerful, or will be able to use a background filter while also supporting multiple video streams and rich application functionality.

Expand Down
15 changes: 7 additions & 8 deletions src/browserbehavior/DefaultBrowserBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
// SPDX-License-Identifier: Apache-2.0

import { detect } from 'detect-browser';
import { UAParser } from 'ua-parser-js';

import BrowserBehavior from './BrowserBehavior';
import ExtendedBrowserBehavior from './ExtendedBrowserBehavior';

export default class DefaultBrowserBehavior implements BrowserBehavior, ExtendedBrowserBehavior {
private readonly browser = detect();
private readonly uaParserResult =
navigator && navigator.userAgent ? new UAParser(navigator.userAgent).getResult() : null;

private browserSupport: { [id: string]: number } = {
chrome: 78,
Expand Down Expand Up @@ -61,10 +64,6 @@ export default class DefaultBrowserBehavior implements BrowserBehavior, Extended
return this.browser.name;
}

os(): string {
return this.browser.os;
}

hasChromiumWebRTC(): boolean {
for (const browser of this.chromeLike) {
if (browser === this.browser.name) {
Expand Down Expand Up @@ -92,10 +91,10 @@ export default class DefaultBrowserBehavior implements BrowserBehavior, Extended
}

supportsCanvasCapturedStreamPlayback(): boolean {
return (!this.isIOSSafari() && !this.isIOSChrome() && !this.isIOSFirefox()) ||
(this.isIOSSafari() && this.majorVersion() > 15) ||
(this.isIOSChrome() && this.majorVersion() > 105) ||
(this.isIOSFirefox() && this.majorVersion() > 104);
return (
(!this.isIOSSafari() && !this.isIOSChrome() && !this.isIOSFirefox()) ||
parseInt(this.uaParserResult.os.version.split('.')[0]) > 15
);
}

supportsBackgroundFilter(): boolean {
Expand Down
6 changes: 6 additions & 0 deletions test/backgroundprocessor/BackgroundBlurProcessor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,12 @@ describe('BackgroundBlurProcessor', () => {
const supportedUserAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3865.75 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)' +
' Version/16.0 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)' +
' CriOS/107.0.5304.66 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)' +
' FxiOS/106.0 Mobile/15E148 Safari/605.1.15',
];
for (const supportedUserAgent of supportedUserAgents) {
setUserAgent(supportedUserAgent);
Expand Down
11 changes: 11 additions & 0 deletions test/browserbehavior/DefaultBrowserBehavior.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ describe('DefaultBrowserBehavior', () => {
mockBuilder.cleanup();
});

describe('construction', () => {
it('can be constructed without user agent', () => {
const userAgent = navigator.userAgent;
// @ts-ignore
delete navigator.userAgent;
new DefaultBrowserBehavior();
// @ts-ignore
navigator.userAgent = userAgent;
});
});

describe('platforms', () => {
it('can detect Firefox', () => {
setUserAgent(FIREFOX_MAC_USER_AGENT);
Expand Down
4 changes: 3 additions & 1 deletion test/task/CleanRestartedSessionTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as chai from 'chai';
import { NScaleVideoUplinkBandwidthPolicy } from '../../src';
import AudioVideoControllerState from '../../src/audiovideocontroller/AudioVideoControllerState';
import NoOpAudioVideoController from '../../src/audiovideocontroller/NoOpAudioVideoController';
import BrowserBehavior from '../../src/browserbehavior/BrowserBehavior';
import DefaultBrowserBehavior from '../../src/browserbehavior/DefaultBrowserBehavior';
import ConnectionHealthData from '../../src/connectionhealthpolicy/ConnectionHealthData';
import SignalingAndMetricsConnectionMonitor from '../../src/connectionmonitor/SignalingAndMetricsConnectionMonitor';
Expand Down Expand Up @@ -37,11 +38,12 @@ describe('CleanRestartedSessionTask', () => {
let domMockBuilder: DOMMockBuilder;
let domMockBehavior: DOMMockBehavior;
let task: Task;
const browserBehavior = new DefaultBrowserBehavior();
let browserBehavior: BrowserBehavior;

beforeEach(() => {
domMockBehavior = new DOMMockBehavior();
domMockBuilder = new DOMMockBuilder(domMockBehavior);
browserBehavior = new DefaultBrowserBehavior();
context = new AudioVideoControllerState();
context.audioVideoController = new NoOpAudioVideoController();
context.logger = context.audioVideoController.logger;
Expand Down
4 changes: 3 additions & 1 deletion test/task/CleanStoppedSessionTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import AudioVideoController from '../../src/audiovideocontroller/AudioVideoContr
import AudioVideoControllerState from '../../src/audiovideocontroller/AudioVideoControllerState';
import NoOpAudioVideoController from '../../src/audiovideocontroller/NoOpAudioVideoController';
import FullJitterBackoff from '../../src/backoff/FullJitterBackoff';
import BrowserBehavior from '../../src/browserbehavior/BrowserBehavior';
import DefaultBrowserBehavior from '../../src/browserbehavior/DefaultBrowserBehavior';
import ConnectionMonitor from '../../src/connectionmonitor/ConnectionMonitor';
import Logger from '../../src/logger/Logger';
Expand Down Expand Up @@ -40,10 +41,10 @@ describe('CleanStoppedSessionTask', () => {
const RECONNECT_SHORT_BACKOFF_MS = 1 * 1000;
const RECONNECT_LONG_BACKOFF_MS = 5 * 1000;
const behavior = new DOMMockBehavior();
const browserBehavior = new DefaultBrowserBehavior();

let context: AudioVideoControllerState;
let domMockBuilder: DOMMockBuilder | null = null;
let browserBehavior: BrowserBehavior;
let task: Task;
let webSocketAdapter: DefaultWebSocketAdapter;
let signalingClient: SignalingClient;
Expand All @@ -66,6 +67,7 @@ describe('CleanStoppedSessionTask', () => {

beforeEach(async () => {
domMockBuilder = new DOMMockBuilder(behavior);
browserBehavior = new DefaultBrowserBehavior();
context = new AudioVideoControllerState();
context.audioVideoController = new NoOpAudioVideoController();
context.logger = context.audioVideoController.logger;
Expand Down
3 changes: 2 additions & 1 deletion test/task/CreatePeerConnectionTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('CreatePeerConnectionTask', () => {
let context: AudioVideoControllerState;
let domMockBuilder: DOMMockBuilder | null = null;
let task: Task;
const browser: BrowserBehavior = new DefaultBrowserBehavior();
let browser: BrowserBehavior;

function makeICEEvent(candidateStr: string | null): RTCPeerConnectionIceEvent {
let iceCandidate: RTCIceCandidate = null;
Expand All @@ -61,6 +61,7 @@ describe('CreatePeerConnectionTask', () => {
beforeEach(() => {
domMockBehavior = new DOMMockBehavior();
domMockBuilder = new DOMMockBuilder(domMockBehavior);
browser = new DefaultBrowserBehavior();

context = new AudioVideoControllerState();
context.audioVideoController = new NoOpAudioVideoController();
Expand Down
3 changes: 2 additions & 1 deletion test/task/CreateSDPTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ describe('CreateSDPTask', () => {
let context: AudioVideoControllerState;
let domMockBuilder: DOMMockBuilder;
let domMockBehavior: DOMMockBehavior;
const browser: BrowserBehavior = new DefaultBrowserBehavior();
let browser: BrowserBehavior;
let task: Task;

beforeEach(() => {
domMockBehavior = new DOMMockBehavior();
domMockBuilder = new DOMMockBuilder(domMockBehavior);
browser = new DefaultBrowserBehavior();
context = new AudioVideoControllerState();
context.audioVideoController = new NoOpAudioVideoController();
context.transceiverController = new DefaultTransceiverController(context.logger, browser);
Expand Down
3 changes: 2 additions & 1 deletion test/task/LeaveAndReceiveLeaveAckTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('LeaveAndReceiveLeaveAckTask', () => {
let signalingClient: SignalingClient;
let leaveAckBuffer: Uint8Array;
let request: SignalingClientConnectionRequest;
const browser: BrowserBehavior = new DefaultBrowserBehavior();
let browser: BrowserBehavior;

function makeLeaveAckFrame(): Uint8Array {
const frame = SdkLeaveAckFrame.create();
Expand All @@ -50,6 +50,7 @@ describe('LeaveAndReceiveLeaveAckTask', () => {

beforeEach(() => {
domMockBuilder = new DOMMockBuilder(behavior);
browser = new DefaultBrowserBehavior();
context = new AudioVideoControllerState();
context.audioVideoController = new NoOpAudioVideoController();
context.transceiverController = new DefaultTransceiverController(context.logger, browser);
Expand Down
3 changes: 2 additions & 1 deletion test/task/PromoteToPrimaryMeetingTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('PromoteToPrimaryMeetingTask', () => {
let signalingClient: SignalingClient;
let primaryMeetingJoinAck: Uint8Array;
let request: SignalingClientConnectionRequest;
const browser: BrowserBehavior = new DefaultBrowserBehavior();
let browser: BrowserBehavior;

function makePrimaryMeetingJoinAckFrame(errorStatus: number = undefined): Uint8Array {
const frame = SdkPrimaryMeetingJoinAckFrame.create();
Expand All @@ -61,6 +61,7 @@ describe('PromoteToPrimaryMeetingTask', () => {

beforeEach(() => {
domMockBuilder = new DOMMockBuilder(behavior);
browser = new DefaultBrowserBehavior();
context = new AudioVideoControllerState();
context.audioVideoController = new NoOpAudioVideoController();
context.transceiverController = new DefaultTransceiverController(context.logger, browser);
Expand Down

0 comments on commit 7f69440

Please sign in to comment.