From bbe2233f08ec848bfd8ccccd0894384712956654 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 4 Sep 2020 04:17:51 -0700 Subject: [PATCH] feat(chromium): use bundled ffmpeg instead of npm deps (#3771) Fixes #3680 --- browsers.json | 2 +- package-lock.json | 56 ---------------------------- package.json | 1 - src/server/chromium/chromium.ts | 6 ++- src/server/chromium/crBrowser.ts | 12 +++--- src/server/chromium/crPage.ts | 6 ++- src/server/chromium/videoRecorder.ts | 14 +++---- src/server/electron/electron.ts | 2 +- src/utils/browserPaths.ts | 12 ++++++ 9 files changed, 37 insertions(+), 74 deletions(-) diff --git a/browsers.json b/browsers.json index 88eb85c548..811f4655ae 100644 --- a/browsers.json +++ b/browsers.json @@ -3,7 +3,7 @@ "browsers": [ { "name": "chromium", - "revision": "792639", + "revision": "792639.1", "download": true }, { diff --git a/package-lock.json b/package-lock.json index c2cfe75299..db58864906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1081,62 +1081,6 @@ "sumchecker": "^3.0.1" } }, - "@ffmpeg-installer/darwin-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz", - "integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==", - "optional": true - }, - "@ffmpeg-installer/ffmpeg": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.0.20.tgz", - "integrity": "sha512-wbgd//6OdwbFXYgV68ZyKrIcozEQpUKlvV66XHaqO2h3sFbX0jYLzx62Q0v8UcFWN21LoxT98NU2P+K0OWsKNA==", - "requires": { - "@ffmpeg-installer/darwin-x64": "4.1.0", - "@ffmpeg-installer/linux-arm": "4.1.3", - "@ffmpeg-installer/linux-arm64": "4.1.4", - "@ffmpeg-installer/linux-ia32": "4.1.0", - "@ffmpeg-installer/linux-x64": "4.1.0", - "@ffmpeg-installer/win32-ia32": "4.1.0", - "@ffmpeg-installer/win32-x64": "4.1.0" - } - }, - "@ffmpeg-installer/linux-arm": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz", - "integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==", - "optional": true - }, - "@ffmpeg-installer/linux-arm64": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz", - "integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==", - "optional": true - }, - "@ffmpeg-installer/linux-ia32": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz", - "integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==", - "optional": true - }, - "@ffmpeg-installer/linux-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz", - "integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==", - "optional": true - }, - "@ffmpeg-installer/win32-ia32": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz", - "integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==", - "optional": true - }, - "@ffmpeg-installer/win32-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz", - "integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==", - "optional": true - }, "@jest/types": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", diff --git a/package.json b/package.json index 3e62eea03f..435b745d30 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ }, "license": "Apache-2.0", "dependencies": { - "@ffmpeg-installer/ffmpeg": "^1.0.20", "debug": "^4.1.1", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.0", diff --git a/src/server/chromium/chromium.ts b/src/server/chromium/chromium.ts index ca0e688d38..e708444db8 100644 --- a/src/server/chromium/chromium.ts +++ b/src/server/chromium/chromium.ts @@ -24,6 +24,7 @@ import { rewriteErrorMessage } from '../../utils/stackTrace'; import { BrowserTypeBase } from '../browserType'; import { ConnectionTransport, ProtocolRequest } from '../transport'; import type { BrowserDescriptor } from '../../utils/browserPaths'; +import { browserDirectory, browsersPath, ffmpegPath} from '../../utils/browserPaths'; import { CRDevTools } from './crDevTools'; import { BrowserOptions } from '../browser'; import * as types from '../types'; @@ -32,6 +33,7 @@ import { isDebugMode, getFromENV } from '../../utils/utils'; export class Chromium extends BrowserTypeBase { private _devtools: CRDevTools | undefined; private _debugPort: number | undefined; + private _ffmpegPath: string; constructor(packagePath: string, browser: BrowserDescriptor) { const debugPortStr = getFromENV('PLAYWRIGHT_CHROMIUM_DEBUG_PORT'); @@ -43,6 +45,8 @@ export class Chromium extends BrowserTypeBase { super(packagePath, browser, debugPort ? { webSocketRegex: /^DevTools listening on (ws:\/\/.*)$/, stream: 'stderr' } : null); this._debugPort = debugPort; + const browserDir = browserDirectory(browsersPath(packagePath), browser); + this._ffmpegPath = ffmpegPath(browserDir, browser); if (isDebugMode()) this._devtools = this._createDevTools(); } @@ -57,7 +61,7 @@ export class Chromium extends BrowserTypeBase { devtools = this._createDevTools(); await (options as any).__testHookForDevTools(devtools); } - return CRBrowser.connect(transport, options, devtools); + return CRBrowser.connect(transport, options, this._ffmpegPath, devtools); } _rewriteStartupError(error: Error): Error { diff --git a/src/server/chromium/crBrowser.ts b/src/server/chromium/crBrowser.ts index 35385cd0ba..6c0d5a45c1 100644 --- a/src/server/chromium/crBrowser.ts +++ b/src/server/chromium/crBrowser.ts @@ -44,10 +44,11 @@ export class CRBrowser extends Browser { private _tracingRecording = false; private _tracingPath: string | null = ''; private _tracingClient: CRSession | undefined; + private _ffmpegPath: string; - static async connect(transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise { + static async connect(transport: ConnectionTransport, options: BrowserOptions, ffmpegPath: string, devtools?: CRDevTools): Promise { const connection = new CRConnection(transport); - const browser = new CRBrowser(connection, options); + const browser = new CRBrowser(connection, options, ffmpegPath); browser._devtools = devtools; const session = connection.rootSession; const version = await session.send('Browser.getVersion'); @@ -88,9 +89,10 @@ export class CRBrowser extends Browser { return browser; } - constructor(connection: CRConnection, options: BrowserOptions) { + constructor(connection: CRConnection, options: BrowserOptions, ffmpegPath: string) { super(options); this._connection = connection; + this._ffmpegPath = ffmpegPath; this._session = this._connection.rootSession; this._connection.on(ConnectionEvents.Disconnected, () => this._didClose()); this._session.on('Target.attachedToTarget', this._onAttachedToTarget.bind(this)); @@ -146,7 +148,7 @@ export class CRBrowser extends Browser { assert(!this._serviceWorkers.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId); if (targetInfo.type === 'background_page') { - const backgroundPage = new CRPage(session, targetInfo.targetId, context, null, false); + const backgroundPage = new CRPage(session, targetInfo.targetId, context, null, false, this._ffmpegPath); this._backgroundPages.set(targetInfo.targetId, backgroundPage); backgroundPage.pageOrError().then(() => { context!.emit(CRBrowserContext.CREvents.BackgroundPage, backgroundPage._page); @@ -156,7 +158,7 @@ export class CRBrowser extends Browser { if (targetInfo.type === 'page') { const opener = targetInfo.openerId ? this._crPages.get(targetInfo.openerId) || null : null; - const crPage = new CRPage(session, targetInfo.targetId, context, opener, true); + const crPage = new CRPage(session, targetInfo.targetId, context, opener, true, this._ffmpegPath); this._crPages.set(targetInfo.targetId, crPage); crPage.pageOrError().then(pageOrError => { const page = crPage._page; diff --git a/src/server/chromium/crPage.ts b/src/server/chromium/crPage.ts index 46b3cbc5ac..805a04e3a0 100644 --- a/src/server/chromium/crPage.ts +++ b/src/server/chromium/crPage.ts @@ -56,6 +56,7 @@ export class CRPage implements PageDelegate { readonly _browserContext: CRBrowserContext; private readonly _pagePromise: Promise; _initializedPage: Page | null = null; + readonly _ffmpegPath: string; // Holds window features for the next popup being opened via window.open, // until the popup target arrives. This could be racy if two oopifs @@ -64,9 +65,10 @@ export class CRPage implements PageDelegate { // of new popup targets. readonly _nextWindowOpenPopupFeatures: string[][] = []; - constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, hasUIWindow: boolean) { + constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, hasUIWindow: boolean, ffmpegPath: string) { this._targetId = targetId; this._opener = opener; + this._ffmpegPath = ffmpegPath; this.rawKeyboard = new RawKeyboardImpl(client, browserContext._browser._isMac); this.rawMouse = new RawMouseImpl(client); this._pdf = new CRPDF(client); @@ -750,7 +752,7 @@ class FrameSession { async _startScreencast(screencastId: string, options: types.PageScreencastOptions): Promise { if (this._screencastState !== 'stopped') throw new Error('Already started'); - const videoRecorder = await VideoRecorder.launch(options); + const videoRecorder = await VideoRecorder.launch(this._crPage._ffmpegPath, options); this._screencastState = 'starting'; try { await this._client.send('Page.startScreencast', { diff --git a/src/server/chromium/videoRecorder.ts b/src/server/chromium/videoRecorder.ts index 3651ede0db..a9123f44c9 100644 --- a/src/server/chromium/videoRecorder.ts +++ b/src/server/chromium/videoRecorder.ts @@ -30,20 +30,22 @@ export class VideoRecorder { private _lastFrameBuffer: Buffer | null = null; private _lastWriteTimestamp: number = 0; private readonly _progress: Progress; + private readonly _ffmpegPath: string; - static async launch(options: types.PageScreencastOptions): Promise { + static async launch(ffmpegPath: string, options: types.PageScreencastOptions): Promise { if (!options.outputFile.endsWith('.webm')) throw new Error('File must have .webm extension'); return await runAbortableTask(async progress => { - const recorder = new VideoRecorder(progress); + const recorder = new VideoRecorder(ffmpegPath, progress); await recorder._launch(options); return recorder; }, 0, 'browser'); } - private constructor(progress: Progress) { + private constructor(ffmpegPath: string, progress: Progress) { this._progress = progress; + this._ffmpegPath = ffmpegPath; this._lastWritePromise = Promise.resolve(); } @@ -54,10 +56,8 @@ export class VideoRecorder { const args = `-loglevel error -f image2pipe -c:v mjpeg -i - -y -an -r ${fps} -c:v vp8 -vf pad=${w}:${h}:0:0:gray,crop=${w}:${h}:0:0`.split(' '); args.push(options.outputFile); const progress = this._progress; - // Use ffmpeg provided by the host system. - const ffmpegPath = process.platform === 'linux' ? 'ffmpeg' : require('@ffmpeg-installer/ffmpeg').path; const { launchedProcess, gracefullyClose } = await launchProcess({ - executablePath: ffmpegPath, + executablePath: this._ffmpegPath, args, pipeStdin: true, progress, @@ -126,4 +126,4 @@ export class VideoRecorder { private _isRunning(): boolean { return !!this._gracefullyClose; } -} \ No newline at end of file +} diff --git a/src/server/electron/electron.ts b/src/server/electron/electron.ts index ddc6918898..35020e7de7 100644 --- a/src/server/electron/electron.ts +++ b/src/server/electron/electron.ts @@ -184,7 +184,7 @@ export class Electron { close: gracefullyClose, kill }; - const browser = await CRBrowser.connect(chromeTransport, { name: 'electron', headful: true, persistent: { noDefaultViewport: true }, browserProcess }); + const browser = await CRBrowser.connect(chromeTransport, { name: 'electron', headful: true, persistent: { noDefaultViewport: true }, browserProcess }, ''); app = new ElectronApplication(browser, nodeConnection); await app._init(); return app; diff --git a/src/utils/browserPaths.ts b/src/utils/browserPaths.ts index 151d2bdc4b..196b9cafb4 100644 --- a/src/utils/browserPaths.ts +++ b/src/utils/browserPaths.ts @@ -72,6 +72,18 @@ export function windowsExeAndDllDirectories(browserPath: string, browser: Browse return []; } +export function ffmpegPath(browserPath: string, browser: BrowserDescriptor): string { + if (browser.name !== 'chromium') + return ''; + if (os.platform() === 'linux') + return 'ffmpeg'; + if (os.platform() === 'win32') + return path.join(browserPath, 'chrome-win', 'ffmpeg.exe'); + if (os.platform() === 'darwin') + return path.join(browserPath, 'chrome-mac', 'ffmpeg'); + return ''; +} + export function executablePath(browserPath: string, browser: BrowserDescriptor): string | undefined { let tokens: string[] | undefined; if (browser.name === 'chromium') {