screencast.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. "use strict";
  2. var __create = Object.create;
  3. var __defProp = Object.defineProperty;
  4. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  5. var __getOwnPropNames = Object.getOwnPropertyNames;
  6. var __getProtoOf = Object.getPrototypeOf;
  7. var __hasOwnProp = Object.prototype.hasOwnProperty;
  8. var __export = (target, all) => {
  9. for (var name in all)
  10. __defProp(target, name, { get: all[name], enumerable: true });
  11. };
  12. var __copyProps = (to, from, except, desc) => {
  13. if (from && typeof from === "object" || typeof from === "function") {
  14. for (let key of __getOwnPropNames(from))
  15. if (!__hasOwnProp.call(to, key) && key !== except)
  16. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  17. }
  18. return to;
  19. };
  20. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  21. // If the importer is in node compatibility mode or this is not an ESM
  22. // file that has been converted to a CommonJS file using a Babel-
  23. // compatible transform (i.e. "__esModule" has not been set), then set
  24. // "default" to the CommonJS "module.exports" for node compatibility.
  25. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  26. mod
  27. ));
  28. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  29. var screencast_exports = {};
  30. __export(screencast_exports, {
  31. Screencast: () => Screencast
  32. });
  33. module.exports = __toCommonJS(screencast_exports);
  34. var import_path = __toESM(require("path"));
  35. var import_utils = require("../utils");
  36. var import_utils2 = require("../utils");
  37. var import_videoRecorder = require("./videoRecorder");
  38. var import_page = require("./page");
  39. var import_registry = require("./registry");
  40. class Screencast {
  41. constructor(page) {
  42. this._videoRecorder = null;
  43. this._videoId = null;
  44. this._screencastClients = /* @__PURE__ */ new Set();
  45. // Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
  46. // When throttling for tracing, 200ms between frames, except for 10 frames around the action.
  47. this._frameThrottler = new FrameThrottler(10, 35, 200);
  48. this._frameListener = null;
  49. this._page = page;
  50. }
  51. stopFrameThrottler() {
  52. this._frameThrottler.dispose();
  53. }
  54. setOptions(options) {
  55. this._setOptions(options).catch((e) => import_utils2.debugLogger.log("error", e));
  56. this._frameThrottler.setThrottlingEnabled(!!options);
  57. }
  58. throttleFrameAck(ack) {
  59. this._frameThrottler.ack(ack);
  60. }
  61. temporarilyDisableThrottling() {
  62. this._frameThrottler.recharge();
  63. }
  64. launchVideoRecorder() {
  65. const recordVideo = this._page.browserContext._options.recordVideo;
  66. if (!recordVideo)
  67. return void 0;
  68. (0, import_utils.assert)(!this._videoId);
  69. this._videoId = (0, import_utils.createGuid)();
  70. const outputFile = import_path.default.join(recordVideo.dir, this._videoId + ".webm");
  71. const videoOptions = {
  72. // validateBrowserContextOptions ensures correct video size.
  73. ...recordVideo.size,
  74. outputFile
  75. };
  76. const ffmpegPath = import_registry.registry.findExecutable("ffmpeg").executablePathOrDie(this._page.browserContext._browser.sdkLanguage());
  77. this._videoRecorder = new import_videoRecorder.VideoRecorder(ffmpegPath, videoOptions);
  78. this._frameListener = import_utils.eventsHelper.addEventListener(this._page, import_page.Page.Events.ScreencastFrame, (frame) => this._videoRecorder.writeFrame(frame.buffer, frame.frameSwapWallTime / 1e3));
  79. this._page.waitForInitializedOrError().then((p) => {
  80. if (p instanceof Error)
  81. this.stopVideoRecording().catch(() => {
  82. });
  83. });
  84. return videoOptions;
  85. }
  86. async startVideoRecording(options) {
  87. const videoId = this._videoId;
  88. (0, import_utils.assert)(videoId);
  89. this._page.once(import_page.Page.Events.Close, () => this.stopVideoRecording().catch(() => {
  90. }));
  91. const gotFirstFrame = new Promise((f) => this._page.once(import_page.Page.Events.ScreencastFrame, f));
  92. await this._startScreencast(this._videoRecorder, {
  93. quality: 90,
  94. width: options.width,
  95. height: options.height
  96. });
  97. gotFirstFrame.then(() => {
  98. this._page.browserContext._browser._videoStarted(this._page.browserContext, videoId, options.outputFile, this._page.waitForInitializedOrError());
  99. });
  100. }
  101. async stopVideoRecording() {
  102. if (!this._videoId)
  103. return;
  104. if (this._frameListener)
  105. import_utils.eventsHelper.removeEventListeners([this._frameListener]);
  106. this._frameListener = null;
  107. const videoId = this._videoId;
  108. this._videoId = null;
  109. const videoRecorder = this._videoRecorder;
  110. this._videoRecorder = null;
  111. await this._stopScreencast(videoRecorder);
  112. await videoRecorder.stop();
  113. const video = this._page.browserContext._browser._takeVideo(videoId);
  114. video?.reportFinished();
  115. }
  116. async _setOptions(options) {
  117. if (options)
  118. await this._startScreencast(this, options);
  119. else
  120. await this._stopScreencast(this);
  121. }
  122. async _startScreencast(client, options) {
  123. this._screencastClients.add(client);
  124. if (this._screencastClients.size === 1) {
  125. await this._page.delegate.startScreencast({
  126. width: options.width,
  127. height: options.height,
  128. quality: options.quality
  129. });
  130. }
  131. }
  132. async _stopScreencast(client) {
  133. this._screencastClients.delete(client);
  134. if (!this._screencastClients.size)
  135. await this._page.delegate.stopScreencast();
  136. }
  137. }
  138. class FrameThrottler {
  139. constructor(nonThrottledFrames, defaultInterval, throttlingInterval) {
  140. this._acks = [];
  141. this._throttlingEnabled = false;
  142. this._nonThrottledFrames = nonThrottledFrames;
  143. this._budget = nonThrottledFrames;
  144. this._defaultInterval = defaultInterval;
  145. this._throttlingInterval = throttlingInterval;
  146. this._tick();
  147. }
  148. dispose() {
  149. if (this._timeoutId) {
  150. clearTimeout(this._timeoutId);
  151. this._timeoutId = void 0;
  152. }
  153. }
  154. setThrottlingEnabled(enabled) {
  155. this._throttlingEnabled = enabled;
  156. }
  157. recharge() {
  158. for (const ack of this._acks)
  159. ack();
  160. this._acks = [];
  161. this._budget = this._nonThrottledFrames;
  162. if (this._timeoutId) {
  163. clearTimeout(this._timeoutId);
  164. this._tick();
  165. }
  166. }
  167. ack(ack) {
  168. if (!this._timeoutId) {
  169. ack();
  170. return;
  171. }
  172. this._acks.push(ack);
  173. }
  174. _tick() {
  175. const ack = this._acks.shift();
  176. if (ack) {
  177. --this._budget;
  178. ack();
  179. }
  180. if (this._throttlingEnabled && this._budget <= 0) {
  181. this._timeoutId = setTimeout(() => this._tick(), this._throttlingInterval);
  182. } else {
  183. this._timeoutId = setTimeout(() => this._tick(), this._defaultInterval);
  184. }
  185. }
  186. }
  187. // Annotate the CommonJS export names for ESM import in node:
  188. 0 && (module.exports = {
  189. Screencast
  190. });