From 6451b1aa6ab76829bde4ccf0d25b1b9668ed3307 Mon Sep 17 00:00:00 2001 From: eric2788 Date: Sat, 30 Mar 2024 01:07:51 +0800 Subject: [PATCH] finished recorder feature and test cases --- .github/workflows/build-test.yml | 22 +- .github/workflows/partial-test.yml | 14 +- CONTRIBUTING.md | 1 + assets/ffmpeg/mt-core.js | 20050 ++++++++++++++++ assets/ffmpeg/mt-worker.js | 154 + .../worker.js} | 0 package.json | 5 +- playwright.config.ts | 2 +- pnpm-lock.yaml | 16 +- src/background/forwards.ts | 8 +- src/background/forwards/stream-content.ts | 28 + src/background/messages/open-tab.ts | 12 +- src/contents/index/components/ButtonList.tsx | 2 +- src/database/migrations.ts | 7 + src/database/tables/stream.d.ts | 13 + src/features/jimaku/components/ButtonArea.tsx | 5 +- .../recorder/components/RecorderButton.tsx | 65 + .../recorder/components/RecorderLayer.tsx | 219 +- src/features/recorder/index.tsx | 3 +- src/features/recorder/recorders/buffer.ts | 95 + src/features/recorder/recorders/index.ts | 26 + src/ffmpeg/core-mt.ts | 50 + src/ffmpeg/core.ts | 22 + src/ffmpeg/index.ts | 18 + src/hooks/ffmpeg.ts | 152 + src/hooks/forwarder.ts | 23 +- src/hooks/life-cycle.ts | 32 +- src/hooks/stream.ts | 172 + src/hooks/styles.ts | 21 + src/logger.ts | 2 +- src/players/flv.ts | 119 +- src/players/hls.ts | 96 +- src/players/index.ts | 68 +- src/settings/components/HotKeyInput.tsx | 4 +- src/settings/features/jimaku/index.tsx | 2 +- src/settings/features/recorder/index.tsx | 99 +- src/settings/fragments/developer.tsx | 6 + src/settings/fragments/features.tsx | 4 +- src/tabs/encoder.tsx | 209 + src/tabs/stream.tsx | 46 +- src/types/extends/global.d.ts | 8 + src/types/media/index.ts | 2 + src/types/media/player.ts | 43 + src/types/media/recorder.ts | 40 + src/utils/binary.ts | 85 + src/utils/ffmpeg.ts | 40 - src/utils/file.ts | 2 +- src/utils/misc.ts | 3 +- tests/content.spec.ts | 54 +- tests/features/jimaku.spec.ts | 41 +- tests/features/recorder.spec.ts | 422 +- tests/features/superchat.spec.ts | 40 +- tests/fixtures/background.ts | 6 - tests/fixtures/component.ts | 8 +- tests/fixtures/content.ts | 10 +- tests/helpers/bilibili-api.ts | 2 + .../{record.spec.ts => player.spec.ts} | 99 +- tests/modules/player.js | 2 +- tests/pages/encoder.spec.ts | 15 + tests/pages/settings.spec.ts | 34 +- tests/theme.setup.ts | 1 + tests/types/movie.ts | 45 + tests/units/buffer.spec.ts | 148 + tests/units/ffmpeg.spec.ts | 80 +- tests/units/stream.spec.ts | 82 - tests/utils/file.ts | 32 +- tests/utils/playwright.ts | 59 +- tsconfig.json | 4 +- 68 files changed, 22711 insertions(+), 588 deletions(-) create mode 100644 assets/ffmpeg/mt-core.js create mode 100644 assets/ffmpeg/mt-worker.js rename assets/{ffmpeg-web-worker.js => ffmpeg/worker.js} (100%) create mode 100644 src/background/forwards/stream-content.ts create mode 100644 src/database/tables/stream.d.ts create mode 100644 src/features/recorder/components/RecorderButton.tsx create mode 100644 src/features/recorder/recorders/buffer.ts create mode 100644 src/features/recorder/recorders/index.ts create mode 100644 src/ffmpeg/core-mt.ts create mode 100644 src/ffmpeg/core.ts create mode 100644 src/ffmpeg/index.ts create mode 100644 src/hooks/ffmpeg.ts create mode 100644 src/hooks/stream.ts create mode 100644 src/hooks/styles.ts create mode 100644 src/tabs/encoder.tsx create mode 100644 src/types/media/index.ts create mode 100644 src/types/media/player.ts create mode 100644 src/types/media/recorder.ts create mode 100644 src/utils/binary.ts delete mode 100644 src/utils/ffmpeg.ts rename tests/integrations/{record.spec.ts => player.spec.ts} (65%) create mode 100644 tests/pages/encoder.spec.ts create mode 100644 tests/types/movie.ts create mode 100644 tests/units/buffer.spec.ts delete mode 100644 tests/units/stream.spec.ts diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8720913f..b48f12f7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,22 +1,19 @@ name: Build And Test Extensions on: - push: - branches: - - 'develop' - paths: - - 'src/**' - - 'tests/**' - - 'playwright.config.ts' - - 'pnpm-lock.yaml' - - '.github/workflows/build-test.yml' pull_request: - branches: [develop] + branches: [develop, master] types: - opened - reopened - ready_for_review - review_requested + paths: + - 'src/**' + - 'tests/**' + - 'playwright.config.ts' + - 'pnpm-lock.yaml' + - '.github/workflows/build-test.yml' workflow_dispatch: inputs: debug: @@ -45,6 +42,8 @@ jobs: run: pnpm install --frozen-lockfile - name: Build And Package Extensions run: pnpm build --zip --target=${{ matrix.browser }}-mv3 + env: + DEBUG: true # upload to github artifacts for each browser with matrix - name: Upload to GitHub Artifacts uses: actions/upload-artifact@v4 @@ -61,6 +60,9 @@ jobs: - uses: actions/checkout@v4 - name: Ensure No @Scoped Test run: grep -r --include "*.spec.ts" "@scoped" && echo "please remove @scoped from tests" && exit 1 || echo "No @scoped tests found" + - uses: pnpm/action-setup@v2 + with: + version: 8 - name: Setup Node.js uses: actions/setup-node@v4.0.1 with: diff --git a/.github/workflows/partial-test.yml b/.github/workflows/partial-test.yml index 60e59142..75be8927 100644 --- a/.github/workflows/partial-test.yml +++ b/.github/workflows/partial-test.yml @@ -10,7 +10,6 @@ on: - 'tests/**' - 'playwright.config.ts' - 'pnpm-lock.yaml' - - '.github/workflows/build-test.yml' workflow_dispatch: inputs: debug: @@ -76,6 +75,8 @@ jobs: run: pnpm install --frozen-lockfile - name: Build Extension and Prepare Tests run: pnpm test:rebuild + env: + DEBUG: true - name: Cache playwright binaries uses: actions/cache@v3 id: playwright-cache @@ -94,4 +95,13 @@ jobs: --timeout=60000 \ --max-failures=5 env: - DEBUG: true \ No newline at end of file + DEBUG: true + - name: Upload Test Results + if: failure() && steps.test.conclusion != 'skipped' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.browser }}-test-results + path: | + test-results/ + playwright-report/ + if-no-files-found: ignore \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76818ca5..89cb9c63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,6 +76,7 @@ src/ ├── contexts/ # 全局用 React 状态管理。 ├── database/ # 数据库相关代码,包括模型定义和数据库迁移操作。 ├── features/ # 特性模块,每个特性模块包含一组相关的功能。 +├── ffmpeg/ # FFmpeg 相关代码。 ├── hooks/ # 全局用的自定义 React Hooks。 ├── migrations/ # 设定迁移脚本(从MV2到MV3)。 ├── players/ # 直播解析器相关代码。 diff --git a/assets/ffmpeg/mt-core.js b/assets/ffmpeg/mt-core.js new file mode 100644 index 00000000..ab0c22f0 --- /dev/null +++ b/assets/ffmpeg/mt-core.js @@ -0,0 +1,20050 @@ +(function(define){var __define; typeof define === "function" && (__define=define,define=null); +// modules are defined as an array +// [ module function, map of requires ] +// +// map of requires is short require name -> numeric require +// +// anything defined in a previous bundle is accessed via the +// orig method which is the require for previous bundles + +(function (modules, entry, mainEntry, parcelRequireName, globalName) { + /* eslint-disable no-undef */ + var globalObject = + typeof globalThis !== 'undefined' + ? globalThis + : typeof self !== 'undefined' + ? self + : typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : {}; + /* eslint-enable no-undef */ + + // Save the require from previous bundle to this closure if any + var previousRequire = + typeof globalObject[parcelRequireName] === 'function' && + globalObject[parcelRequireName]; + + var cache = previousRequire.cache || {}; + // Do not use `require` to prevent Webpack from trying to bundle this call + var nodeRequire = + typeof module !== 'undefined' && + typeof module.require === 'function' && + module.require.bind(module); + + function newRequire(name, jumped) { + if (!cache[name]) { + if (!modules[name]) { + // if we cannot find the module within our internal map or + // cache jump to the current global require ie. the last bundle + // that was added to the page. + var currentRequire = + typeof globalObject[parcelRequireName] === 'function' && + globalObject[parcelRequireName]; + if (!jumped && currentRequire) { + return currentRequire(name, true); + } + + // If there are other bundles on this page the require from the + // previous one is saved to 'previousRequire'. Repeat this as + // many times as there are bundles until the module is found or + // we exhaust the require chain. + if (previousRequire) { + return previousRequire(name, true); + } + + // Try the node require function if it exists. + if (nodeRequire && typeof name === 'string') { + return nodeRequire(name); + } + + var err = new Error("Cannot find module '" + name + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; + } + + localRequire.resolve = resolve; + localRequire.cache = {}; + + var module = (cache[name] = new newRequire.Module(name)); + + modules[name][0].call( + module.exports, + localRequire, + module, + module.exports, + this + ); + } + + return cache[name].exports; + + function localRequire(x) { + var res = localRequire.resolve(x); + return res === false ? {} : newRequire(res); + } + + function resolve(x) { + var id = modules[name][1][x]; + return id != null ? id : x; + } + } + + function Module(moduleName) { + this.id = moduleName; + this.bundle = newRequire; + this.exports = {}; + } + + newRequire.isParcelRequire = true; + newRequire.Module = Module; + newRequire.modules = modules; + newRequire.cache = cache; + newRequire.parent = previousRequire; + newRequire.register = function (id, exports) { + modules[id] = [ + function (require, module) { + module.exports = exports; + }, + {}, + ]; + }; + + Object.defineProperty(newRequire, 'root', { + get: function () { + return globalObject[parcelRequireName]; + }, + }); + + globalObject[parcelRequireName] = newRequire; + + for (var i = 0; i < entry.length; i++) { + newRequire(entry[i]); + } + + if (mainEntry) { + // Expose entry point to Node, AMD or browser globals + // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js + var mainExports = newRequire(mainEntry); + + // CommonJS + if (typeof exports === 'object' && typeof module !== 'undefined') { + module.exports = mainExports; + + // RequireJS + } else if (typeof define === 'function' && define.amd) { + define(function () { + return mainExports; + }); + + //