|
1 | 1 | # AGENTS.md |
2 | 2 |
|
3 | | -## Project Overview |
4 | | - |
5 | | -dash.js is the DASH Industry Forum reference client for MPEG-DASH playback in browsers. |
6 | | -Pure JavaScript (ES2020), ESM modules (`"type": "module"`), no TypeScript in source code |
7 | | -(TypeScript is only used to validate `index.d.ts`). Node >= 20 required. |
8 | | - |
9 | | -## Build Commands |
10 | | - |
11 | | -```bash |
12 | | -npm run build # Full build: clean, typecheck, test, lint, then webpack (modern + legacy) |
13 | | -npm run build-modern # Clean + typecheck + test + lint + webpack modern only |
14 | | -npm run build-legacy # Clean + typecheck + test + lint + webpack |
15 | | -npm run dev # Typecheck + webpack modern dev (watch mode) |
16 | | -npm start # webpack-dev-server on port 3000 (opens samples/index.html) |
17 | | -npm run lint # ESLint on src/**/*.js and test/unit/{mocks,test}/**/*.js |
18 | | -npm run doc # Generate JSDoc documentation |
19 | | -``` |
20 | | - |
21 | | -## Testing |
22 | | - |
23 | | -**Frameworks:** Karma (runner) + Mocha (describe/it) + Chai (expect/assert) + Sinon (spy/stub/mock) |
24 | | - |
25 | | -```bash |
26 | | -# Run all unit tests (ChromeHeadless + FirefoxHeadless) |
27 | | -npm test |
28 | | - |
29 | | -# Run a single test or subset by grep pattern (matches describe/it names) |
30 | | -npx karma start test/unit/config/karma.unit.conf.cjs --grep="EventBus" |
31 | | -npx karma start test/unit/config/karma.unit.conf.cjs --grep="getOptimalRepresentationForBitrate" |
32 | | - |
33 | | -# Run functional tests |
34 | | -npm run test-functional |
35 | | -``` |
36 | | - |
37 | | -There is no per-file test runner. All unit tests are bundled by Karma/webpack and run |
38 | | -together in a headless browser. Use `--grep` to filter by test name. |
39 | | - |
40 | | -Unit test files live in `test/unit/test/` and mirror the `src/` directory structure. |
41 | | -Test file naming convention uses dot-separated module paths: |
42 | | -- `core.EventBus.js` tests `src/core/EventBus.js` |
43 | | -- `streaming.controllers.AbrController.js` tests `src/streaming/controllers/AbrController.js` |
44 | | -- `dash.models.DashManifestModel.js` tests `src/dash/models/DashManifestModel.js` |
45 | | - |
46 | | -## Code Style |
47 | | - |
48 | | -### Formatting (enforced by ESLint flat config in `eslint.config.mjs`) |
49 | | - |
50 | | -- **Indentation:** 4 spaces (including switch case bodies) |
51 | | -- **Quotes:** Single quotes, template literals allowed |
52 | | -- **Semicolons:** Required |
53 | | -- **Curly braces:** Always required, even for single-line blocks (`curly: 'all'`) |
54 | | -- **Line endings:** LF (see `.editorconfig`) |
55 | | -- **Trailing whitespace:** Trimmed in `.js` files |
56 | | -- **Final newline:** Required in `.js` and `.md` files |
57 | | -- **Keyword spacing:** Space before and after keywords (`if`, `else`, `for`, etc.) |
58 | | -- **Infix operators:** Spaces around operators (`a + b`, not `a+b`) |
59 | | -- **No multi-spaces:** Only single spaces between tokens |
60 | | -- **No Prettier:** Formatting is handled by ESLint rules only |
61 | | - |
62 | | -### Imports |
63 | | - |
64 | | -- ES module `import`/`export` syntax exclusively |
65 | | -- Always include `.js` extension in import paths: `import Foo from './Foo.js'` |
66 | | -- Relative paths for internal imports |
67 | | -- Group order: external dependencies first, then internal modules |
68 | | -- Default exports are the norm; named exports are rare |
69 | | - |
70 | | -### Architecture Pattern — FactoryMaker |
71 | | - |
72 | | -Most modules use the **factory function pattern**, not ES classes: |
73 | | - |
74 | | -```js |
75 | | -function MyController() { |
76 | | - const context = this.context; |
77 | | - let instance, logger, someState; |
78 | | - |
79 | | - function setup() { /* init logic, called at bottom of factory */ } |
80 | | - function _privateMethod() { /* underscore prefix */ } |
81 | | - function publicMethod() { /* no prefix */ } |
82 | | - function reset() { /* cleanup on teardown */ } |
83 | | - |
84 | | - instance = { publicMethod, reset }; |
85 | | - setup(); |
86 | | - return instance; |
87 | | -} |
88 | | -MyController.__dashjs_factory_name = 'MyController'; |
89 | | -export default FactoryMaker.getSingletonFactory(MyController); |
90 | | -``` |
91 | | - |
92 | | -Key conventions: |
93 | | -- **Singletons** (`getSingletonFactory`): one instance per context (controllers, models) |
94 | | -- **Class factories** (`getClassFactory`): new instance each call (value objects, processors) |
95 | | -- **`__dashjs_factory_name`**: required static property for registration, matches the function name |
96 | | -- **`setup()`**: called at the bottom of the factory function for initialization |
97 | | -- **`reset()`**: cleanup method, should restore initial state |
98 | | -- **`setConfig(config)`**: dependency injection method, receives an object with dependencies |
99 | | -- **`instance` object**: the public API; only methods listed here are public |
100 | | - |
101 | | -### Value Objects |
102 | | - |
103 | | -Simple data classes in `src/*/vo/` use ES class syntax with constructor assignments |
104 | | -and `export default ClassName`. See `src/streaming/vo/DashJSError.js` for an example. |
105 | | - |
106 | | -### Naming Conventions |
107 | | - |
108 | | -- **Files:** PascalCase for classes/factories (`AbrController.js`, `MediaPlayer.js`) |
109 | | -- **Private methods:** `_underscore` prefix (`_onQualityChangeRendered`, `_commonOn`) |
110 | | -- **Public methods:** camelCase, no prefix |
111 | | -- **Constants:** UPPER_SNAKE_CASE for module-level constants; constant objects use PascalCase keys |
112 | | -- **Events:** Class-based hierarchy extending `EventsBase`, string constant properties |
113 | | -- **Loggers:** `logger = debug.getLogger(instance)` — use `logger.debug()`, `logger.info()`, `logger.warn()`, `logger.error()` |
114 | | - |
115 | | -### Error Handling |
116 | | - |
117 | | -- Errors are dispatched via `EventBus` as error events, not thrown |
118 | | -- Use `DashJSError` value objects (code + message + data) |
119 | | -- Error codes are defined as constants in `src/core/errors/Errors.js` and `src/streaming/vo/metrics/PlayList.js` |
120 | | -- Critical errors trigger `Events.ERROR`; check `error.code` to distinguish types |
121 | | - |
122 | | -### License Header |
123 | | - |
124 | | -Every source file must include the BSD-3-Clause license header (approximately 30 lines) |
125 | | -at the top of the file. See any existing source file for the exact text. |
126 | | - |
127 | | -## Test Conventions |
128 | | - |
129 | | -Tests follow this general pattern: import module + mocks, create `const context = {}`, |
130 | | -instantiate singletons with `Module(context).getInstance()`, inject mocks via `setConfig()`, |
131 | | -call `initialize()` in `beforeEach`, and call `reset()` in `afterEach`. Tests use nested |
132 | | -`describe` blocks (one per method) and `it('Should ...', function () { ... })` blocks. |
133 | | - |
134 | | -- **Mocks:** Hand-written in `test/unit/mocks/`, each mirrors the real class API |
135 | | -- **Helpers:** `test/unit/helpers/` — `ObjectsHelper`, `VOHelper`, `SpecHelper` create dummy objects |
136 | | -- **Assertions:** Chai `expect` style preferred; `assert` also used |
137 | | -- **Spying/stubbing:** Sinon (`sinon.spy()`, `sinon.stub()`) |
138 | | -- **Context:** Each test suite creates `const context = {}` and instantiates singletons against it |
139 | | -- **Cleanup:** Always call `reset()` on instances, settings, and eventBus in `afterEach` |
140 | | -- **Test data:** Fixtures in `test/unit/data/` (XML manifests, subtitle files, etc.) |
141 | | - |
142 | | -## Project Structure |
143 | | - |
144 | | -``` |
145 | | -src/ |
146 | | -├── core/ # EventBus, FactoryMaker, Settings, Debug, Utils, errors |
147 | | -├── dash/ # DASH-specific: parser, adapter, manifest model, segment handling |
148 | | -├── mss/ # Microsoft Smooth Streaming support |
149 | | -├── offline/ # Offline playback / download support |
150 | | -└── streaming/ # Core player: controllers, models, rules, protection (DRM), text, net |
151 | | -test/ |
152 | | -├── unit/ # Unit tests (Karma + Mocha + Chai) |
153 | | -│ ├── config/ # karma.unit.conf.cjs |
154 | | -│ ├── data/ # Test fixtures (MPDs, subtitles) |
155 | | -│ ├── helpers/ # ObjectsHelper, VOHelper, etc. |
156 | | -│ ├── mocks/ # Hand-written mock classes |
157 | | -│ └── test/ # Test files (mirrors src/ structure) |
158 | | -└── functional/ # Functional/integration tests (real playback) |
159 | | -build/webpack/ # Webpack configs (modern/legacy, dev/prod, UMD/ESM) |
160 | | -``` |
161 | | - |
162 | | -## CI and Contributing |
163 | | - |
164 | | -- PRs target the `development` branch (not `main`/`master`) |
165 | | -- CI runs `npm run build` which executes: clean -> typecheck -> unit tests -> lint -> webpack |
166 | | -- A pre-commit git hook runs `npm run lint` automatically |
167 | | -- Functional tests run on LambdaTest/BrowserStack in CI for cross-browser validation |
168 | | -- Always run `npm run build` before committing to catch test failures and lint errors |
169 | | -- Include BSD-3-Clause header in new files; add/update unit tests for changes |
| 3 | +dash.js — DASH Industry Forum reference client. Pure JS (ES2020), ESM, Node >= 20. |
| 4 | + |
| 5 | +## Gotchas |
| 6 | + |
| 7 | +### Build & Test |
| 8 | +- **`npm run build` skips tests/lint.** Use `npm run build-modern` for the full pipeline (clean + tsc + test + lint + webpack). |
| 9 | +- **No per-file test runner.** All unit tests are bundled by Karma/webpack. Filter with `--grep`: |
| 10 | + ```bash |
| 11 | + npx karma start test/unit/config/karma.unit.conf.cjs --grep="EventBus" |
| 12 | + ``` |
| 13 | +- **Functional tests load from `dist/`**, not source. Build first. |
| 14 | +- **Two entry points:** `index.js` (full) and `index_mediaplayerOnly.js` (lightweight). Public API changes may need both updated. |
| 15 | +- **Dev builds fewer webpack bundles than prod.** See `build/webpack/common/webpack.common.base.cjs`. |
| 16 | + |
| 17 | +### Architecture |
| 18 | +- **Context = DI container.** Each `MediaPlayer` creates its own `context` object. All singletons are scoped to it: `EventBus(context).getInstance()`. This enables multiple independent players on one page. |
| 19 | +- **Errors are events, never thrown.** Use `DashJSError` + `EventBus` (`Events.ERROR`). Codes in `src/core/errors/Errors.js`. |
| 20 | +- **Protection/DRM is opt-in.** Call `player.setProtectionData()` explicitly. |
| 21 | +- **`player.extend()` must be called BEFORE `initialize()`.** |
| 22 | + |
| 23 | +### Code changes |
| 24 | +- **PRs target `development`**, not `main`/`master`. |
| 25 | +- **BSD-3-Clause header required** in every new source file. |
| 26 | +- **v5 API — don't use deprecated v4 methods:** |
| 27 | + - `getBitrateInfoListFor()` → `getRepresentationsByType(type)` |
| 28 | + - `setQualityFor()` → `setRepresentationForTypeByIndex(type, index, forceReplace)` |
| 29 | + - `getQualityFor()` → `getCurrentRepresentationForType(type)` |
| 30 | +- **New settings** need both `Settings.js` (value + JSDoc) and `index.d.ts`. |
| 31 | +- **New samples** must update `samples/samples.json`. |
| 32 | + |
| 33 | +## Where to look |
| 34 | + |
| 35 | +| What | Where | |
| 36 | +|---|---| |
| 37 | +| Build scripts | `package.json` → `scripts` | |
| 38 | +| Lint rules | `eslint.config.mjs` + `.editorconfig` | |
| 39 | +| Architecture pattern | Any `src/` file — look for `FactoryMaker` | |
| 40 | +| Test patterns | Any file in `test/unit/test/` | |
| 41 | +| Test naming | `test/unit/test/` mirrors `src/` with dot-separated paths: `core.EventBus.js` → `src/core/EventBus.js` | |
| 42 | +| Mocks | `test/unit/mocks/` | |
| 43 | +| Test fixtures | `test/unit/data/` | |
| 44 | +| API surface | `src/streaming/MediaPlayer.js` | |
| 45 | +| Public exports | `index.js` + `index_mediaplayerOnly.js` | |
| 46 | +| TypeScript defs | `index.d.ts` | |
| 47 | +| Webpack entries | `build/webpack/common/webpack.common.base.cjs` | |
| 48 | +| Sample registry | `samples/samples.json` | |
| 49 | +| CI pipelines | `.github/workflows/` | |
0 commit comments