ngx-kit 7.3.3 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,349 @@
1
+ # ngx-kit/media-control
2
+
3
+ An Angular-native Audio/Video player: native `<audio>`/`<video>` fast path,
4
+ optional MSE and WASM decoder backends, DI-based decoder plugins, signals
5
+ throughout, SSR- and zoneless-safe.
6
+
7
+ > Migrating from `ngx-audio-control`? Nothing breaks — see
8
+ > [Migrating from `NgxAudioControl`](#migrating-from-ngxaudiocontrol).
9
+
10
+ ## 1. Installation
11
+
12
+ ```
13
+ npm install ngx-kit --save
14
+ ```
15
+
16
+ ## 2. Basic audio usage
17
+
18
+ ```ts
19
+ import { NgxMediaControl } from 'ngx-kit/media-control';
20
+
21
+ @Component({
22
+ imports: [NgxMediaControl],
23
+ template: `<ngx-media-control type="audio" [fileList]="files" [download]="true" />`,
24
+ })
25
+ export class PlayerComponent {
26
+ files = ['assets/a.mp3', 'assets/b.mp3'];
27
+ }
28
+ ```
29
+
30
+ ## 3. Basic video usage
31
+
32
+ ```html
33
+ <ngx-media-control type="video" [sources]="[{ src: 'assets/clip.mp4', poster: 'assets/poster.jpg' }]" />
34
+ ```
35
+
36
+ ## 4. Audio + video configuration
37
+
38
+ `type` is `'audio' | 'video' | 'both'`:
39
+
40
+ - `'audio'` — no video frame is rendered even if a source has a video track.
41
+ - `'video'` — video frame, fullscreen/PiP controls (when the browser supports them).
42
+ - `'both'` — video frame is rendered; degrades to audio-style controls when the active source has no video track.
43
+
44
+ ## 5. Playlist
45
+
46
+ Use `sources` (typed, supports metadata) instead of the legacy `fileList` (plain string URLs) when you need titles/artist/poster/codecs/download URLs:
47
+
48
+ ```ts
49
+ sources: NgxMediaSource[] = [
50
+ { src: 'a.mp3', title: 'Track A', artist: 'Someone' },
51
+ { src: 'b.mp3', title: 'Track B' },
52
+ ];
53
+ ```
54
+
55
+ ## 6. Controls (inputs)
56
+
57
+ | Input | Type | Default | Notes |
58
+ |---|---|---|---|
59
+ | `type` | `'audio' \| 'video' \| 'both'` | `'audio'` | new |
60
+ | `poster` | `string` | — | new, video only |
61
+ | `showList` | `boolean` | `true` | unchanged |
62
+ | `download` | `boolean` | `false` | unchanged |
63
+ | `showFileName` | `boolean` | `true` | unchanged |
64
+ | `showSpeed` | `boolean` | `true` | unchanged |
65
+ | `showVolume` | `boolean` | `true` | unchanged |
66
+ | `linear` | `boolean` | `false` | unchanged |
67
+ | `preload` | `'none' \| 'metadata' \| 'auto'` | `'metadata'` | unchanged |
68
+ | `fetchHeaders` | `HeadersInit` | — | see note below — NOT sent on plain native playback |
69
+ | `fileList` | `string[]` | — | unchanged, still works |
70
+ | `sources` | `NgxMediaSource[]` | — | new, preferred |
71
+
72
+ Outputs: `trackChange` (fires with the active `NgxMediaSource`), `error` (fires with a human-readable message; inspect `NgxMediaError.category` if you're subscribing programmatically via the facade in your own wrapper).
73
+
74
+ > **`fetchHeaders` platform limitation.** A native `<audio src>`/`<video src>`
75
+ > fetch cannot carry arbitrary request headers — that's a browser limitation,
76
+ > not something this library papers over with a hidden `fetch()` + `Blob`
77
+ > step (which would break streaming/range-requests for anything but small
78
+ > clips). `fetchHeaders` is applied as the default `requestHeaders` for any
79
+ > resolved source that doesn't specify its own, and only actually takes
80
+ > effect for sources that end up on a decoder/MSE backend — both of which
81
+ > fetch through `NGX_MEDIA_REQUEST_HANDLER` and can carry headers. For a
82
+ > plain natively-playable URL, set headers via a signed/short-lived URL or a
83
+ > `NGX_MEDIA_REQUEST_HANDLER`-based backend instead.
84
+
85
+ ## 7. Media sources
86
+
87
+ ```ts
88
+ export interface NgxMediaSource {
89
+ src: string | Blob | File | MediaStream;
90
+ type?: string;
91
+ codecs?: string;
92
+ title?: string;
93
+ artist?: string;
94
+ album?: string;
95
+ poster?: string;
96
+ downloadUrl?: string;
97
+ downloadFileName?: string;
98
+ requestHeaders?: HeadersInit;
99
+ }
100
+ ```
101
+
102
+ Playback source and download source are independent (`downloadUrl`) — the
103
+ playback URL is not assumed to be publicly downloadable or vice versa.
104
+
105
+ ## 8. Browser support
106
+
107
+ Chrome, Edge, Firefox, Safari (desktop + iOS), Android WebView. Every
108
+ optional API (`MediaSource`, `AudioContext`, `mediaSession`,
109
+ `requestPictureInPicture`, `requestFullscreen`) is feature-detected at the
110
+ call site (`utils/capabilities.ts`) before use; the UI hides the
111
+ corresponding control when unsupported instead of showing a broken button.
112
+
113
+ ## 9. Native playback
114
+
115
+ The default `MediaEngineFactory` always tries a native `<audio>`/`<video>`
116
+ element first via `HTMLMediaElement.canPlayType()` combined with an explicit
117
+ `type`/`codecs` you supply (never trusting the file extension alone — see
118
+ `utils/capabilities.ts`). This is the fast path for MP3/AAC/WAV/OGG/Opus/WebM/MP4/etc.
119
+ whenever the browser can decode them itself.
120
+
121
+ ## 10. Decoder architecture
122
+
123
+ ```
124
+ Source
125
+ │
126
+ ▼
127
+ Capability detection (canPlayType + type/codecs)
128
+ │
129
+ ├─ native? ───────────► NativeMediaEngine (<audio>/<video>)
130
+ │
131
+ └─ not native ─► NgxMediaDecoderRegistry.findDecoder()
132
+ │
133
+ ├─ found ─► decoder.createEngine() → WASM / MSE / custom engine
134
+ └─ none ─► NgxMediaError('UNSUPPORTED_FORMAT')
135
+ ```
136
+
137
+ The UI (`NgxMediaControl`) never touches an engine directly — it only calls
138
+ `NgxMediaFacade`, which asks `NgxMediaEngineFactory` for a native engine and
139
+ actually attempts `load()` before ever consulting the decoder registry.
140
+ `canPlayType()`/extension/mime are only ever a hint, not a guarantee — a
141
+ mislabeled or fake extension, or a container the browser opens but can't
142
+ actually decode, only shows up as a genuine failure once `load()` is
143
+ attempted, so the decoder registry is tried on a real failure, not on a
144
+ pessimistic upfront guess. Swapping the engine implementation never touches
145
+ the component.
146
+
147
+ ## 11. WASM decoders
148
+
149
+ **Is there an npm package you can install today to try this?** Yes —
150
+ [`@ffmpeg/ffmpeg`](https://www.npmjs.com/package/@ffmpeg/ffmpeg) is the
151
+ general-purpose answer (handles 3GP, WMA, AMR, FLV, and effectively
152
+ anything FFmpeg reads). For narrower needs there are also small pure-WASM
153
+ decoders like `opus-decoder`/`mpg123-decoder` if you specifically only need
154
+ one codec and want a much smaller download than FFmpeg's ~25MB core.
155
+
156
+ ```
157
+ npm install @ffmpeg/ffmpeg @ffmpeg/util @ffmpeg/core
158
+ ```
159
+
160
+ A complete, copy-pasteable decoder built on it is in
161
+ `examples/ffmpeg-transcode-decoder.example.ts` (outside the library's own
162
+ source tree on purpose, so the library itself never depends on `@ffmpeg/*`
163
+ — see below). It transcodes the unsupported source into `wav`/`mp4`
164
+ in-memory and hands the result to `NativeMediaEngine`, reusing all of the
165
+ native engine's playback/seek/event handling instead of reimplementing it.
166
+ Copy that file into your app (e.g. `src/app/decoders/`) and register it:
167
+
168
+ ```ts
169
+ import { provideNgxMediaDecoder } from 'ngx-kit/media-control';
170
+ import { FfmpegTranscodeDecoder } from './decoders/ffmpeg-transcode-decoder.example';
171
+
172
+ bootstrapApplication(AppComponent, {
173
+ providers: [provideNgxMediaDecoder(FfmpegTranscodeDecoder)],
174
+ });
175
+ ```
176
+
177
+
178
+
179
+ `NgxWasmDecoderBase` (in `decoders/wasm-decoder.base.ts`) is the harness: it
180
+ lazily `import()`s your codec module (and optionally runs it in a `Worker`)
181
+ on first real use, so nothing WASM-related ever enters the default bundle.
182
+ **No real codec ships with this library** — bundling e.g. FFmpeg/WASM into
183
+ the core would defeat the "lightweight native fast path" requirement for
184
+ every consumer, including those who never need it. You extend the base class
185
+ with your own `loadModule()`/`buildEngine()`; `DecodedBufferEngine` is
186
+ provided as a ready-made `NgxMediaEngine` for decoders that decode a full
187
+ clip into an `AudioBuffer` and play it via Web Audio.
188
+
189
+ ## 12. Custom decoder development
190
+
191
+ ```ts
192
+ import { NgxMediaDecoder, NgxMediaSource, provideNgxMediaDecoder } from 'ngx-kit/media-control';
193
+
194
+ @Injectable()
195
+ export class My3gpDecoder implements NgxMediaDecoder {
196
+ readonly id = '3gp';
197
+ readonly formats = ['3gp'];
198
+ readonly priority = 0;
199
+
200
+ canDecode(source: NgxMediaSource) {
201
+ return source.type === 'video/3gpp';
202
+ }
203
+
204
+ async createEngine(source: NgxMediaSource) {
205
+ // return any NgxMediaEngine — e.g. extend NgxWasmDecoderBase, or build
206
+ // one by hand around MSE/Web Audio.
207
+ }
208
+
209
+ destroy() {}
210
+ }
211
+ ```
212
+
213
+ ## 13. Angular DI registration
214
+
215
+ ```ts
216
+ bootstrapApplication(AppComponent, {
217
+ providers: [
218
+ provideNgxMedia(), // optional global config
219
+ provideNgxMediaDecoder(My3gpDecoder),
220
+ // provideNgxMediaRequestHandler({ request: (source, signal) => myFetch(source, signal) }),
221
+ ],
222
+ });
223
+ ```
224
+
225
+ `provideNgxMediaDecoder` registers into the `NGX_MEDIA_DECODER` multi
226
+ token; register as many as you like, `priority` (higher first) breaks ties
227
+ when more than one decoder claims the same source.
228
+
229
+ ## 14. Performance
230
+
231
+ - No polling: every state field is driven by a real media event (`timeupdate`, `progress`, `durationchange`, ...).
232
+ - All native listeners are attached inside `NgZone.runOutsideAngular` via the facade; the only thing that can trigger a render is a signal write, which OnPush + signals coalesce naturally — high-frequency events don't cause excess change detection.
233
+ - `AudioContext`/Web Audio graph is created lazily, only if a consumer calls into it.
234
+ - WASM/MSE code paths are behind dynamic `import()`.
235
+
236
+ ## 15. SSR
237
+
238
+ Every browser API access is guarded by `isBrowser()`
239
+ (`utils/capabilities.ts`). `NgxMediaEngineFactory.create()` throws a typed
240
+ `NgxMediaError('BROWSER_UNSUPPORTED', ...)` rather than crashing if somehow
241
+ invoked outside a browser context.
242
+
243
+ ## 16. Zoneless Angular
244
+
245
+ The facade and engines are 100% signal-driven; nothing relies on Zone.js
246
+ patches. `NgZone.runOutsideAngular` is used defensively for zone-based apps
247
+ but is a no-op wrapper under zoneless change detection.
248
+
249
+ ## 17. Web Audio
250
+
251
+ Opt-in only, via `facade.getWebAudioGraph()` inside your own extension —
252
+ gain/analyser nodes are created lazily on first access. Never bypasses
253
+ autoplay policy; `resumeOnUserGesture()` must be called from a real user
254
+ gesture handler.
255
+
256
+ ## 18. MSE
257
+
258
+ `engines/mse-media-engine.ts` is a real, working single-`SourceBuffer`
259
+ progressive-fetch engine, reached only via explicit opt-in
260
+ (`NgxMediaEngineFactory.createMse()`, typically from inside a decoder). It is
261
+ not an ABR/manifest (DASH/HLS) player — that's a decoder plugin's job, built
262
+ on top of this engine.
263
+
264
+ ## 19. Media Session
265
+
266
+ Wired automatically (`facade/media-session.ts`) whenever `navigator.mediaSession`
267
+ exists: play/pause/prev/next/seek actions and `title`/`artist`/`album`/`poster`
268
+ metadata. No-ops everywhere the API is absent.
269
+
270
+ ## 20. Fullscreen
271
+
272
+ Shown automatically for `type="video"`/`"both"` when `document.fullscreenEnabled`
273
+ is true. Double-clicking the video also toggles fullscreen.
274
+
275
+ ## 21. Picture-in-Picture
276
+
277
+ Shown automatically when `document.pictureInPictureEnabled` /
278
+ `HTMLVideoElement.prototype.requestPictureInPicture` exist.
279
+
280
+ ## 22. Error handling
281
+
282
+ ```ts
283
+ export type NgxMediaErrorCategory =
284
+ | 'NETWORK' | 'UNSUPPORTED_FORMAT' | 'DECODER' | 'ABORTED'
285
+ | 'AUTOPLAY_BLOCKED' | 'MEDIA' | 'PERMISSION' | 'BROWSER_UNSUPPORTED' | 'UNKNOWN';
286
+ ```
287
+
288
+ `NgxMediaError` carries a `category` and human-readable `message`; the
289
+ component's `error` output emits the message, and `NgxMediaError.cause`
290
+ keeps the original DOM/browser error for logging without leaking it into the
291
+ template.
292
+
293
+ ## 23. Security limitations
294
+
295
+ Client-side code cannot prevent someone from downloading a file the browser
296
+ can already fetch and play. This library does not implement URL obfuscation
297
+ and call it security. For genuine protection, use short-lived signed URLs
298
+ and/or authenticated requests — `NGX_MEDIA_REQUEST_HANDLER` lets you plug in
299
+ your own authenticated fetcher without coupling the core to `HttpClient`.
300
+
301
+ ## 24. Examples
302
+
303
+ See `demo/media-control/` for a working audio + video showcase, including
304
+ the exact custom-decoder registration snippet from §12.
305
+
306
+ ---
307
+
308
+ ## Seeking support on your server
309
+
310
+
311
+ ### IIS `web.config`
312
+ ```xml
313
+ <system.webServer>
314
+ <httpProtocol>
315
+ <customHeaders>
316
+ <add name="Accept-Ranges" value="bytes" />
317
+ </customHeaders>
318
+ </httpProtocol>
319
+ </system.webServer>
320
+ ```
321
+
322
+ ### Apache `.htaccess`
323
+ ```
324
+ <IfModule mod_headers.c>
325
+ Header set Accept-Ranges bytes
326
+ </IfModule>
327
+ ```
328
+
329
+ ### Nginx `nginx.conf`
330
+ ```
331
+ http {
332
+ server {
333
+ location / {
334
+ add_header Accept-Ranges bytes;
335
+ }
336
+ }
337
+ }
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Migrating from `NgxAudioControl`
343
+
344
+ Nothing is required. `<ngx-audio-control [fileList]="...">` keeps working —
345
+ it's now a thin wrapper around `<ngx-media-control type="audio">` with the
346
+ exact same inputs/defaults. When you're ready, switch the selector to
347
+ `ngx-media-control` and add `type="audio"` to opt into the richer `sources`
348
+ input, `trackChange`/`error` outputs, and future decoder support.
349
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-kit",
3
- "version": "7.3.3",
3
+ "version": "7.4.0",
4
4
  "description": "Angular input components",
5
5
  "author": "Mohammadreza Samani <mr.samani1368@gmail.com>",
6
6
  "license": "MIT",
@@ -97,6 +97,10 @@
97
97
  "types": "./types/ngx-kit-infinite-scroll.d.ts",
98
98
  "default": "./fesm2022/ngx-kit-infinite-scroll.mjs"
99
99
  },
100
+ "./media-control": {
101
+ "types": "./types/ngx-kit-media-control.d.ts",
102
+ "default": "./fesm2022/ngx-kit-media-control.mjs"
103
+ },
100
104
  "./menu": {
101
105
  "types": "./types/ngx-kit-menu.d.ts",
102
106
  "default": "./fesm2022/ngx-kit-menu.mjs"
@@ -330,7 +330,7 @@ declare class NgxCalendarComponent extends NgxDatePickerBase implements OnInit {
330
330
  eventChange: EventEmitter<CalendarEventChange>;
331
331
  eventsChange: EventEmitter<MsEvents<Date>[]>;
332
332
  selected?: Date;
333
- anchorDate: Date;
333
+ anchorDate: _angular_core.WritableSignal<Date | undefined>;
334
334
  eventViewItems: {
335
335
  event: MsEventViewer;
336
336
  start: Date;
@@ -232,10 +232,9 @@ declare class Dialog {
232
232
  declare const DIALOG_DATA: InjectionToken<any>;
233
233
  declare const DIALOG_REF: InjectionToken<NgxDialogRef<any>>;
234
234
  /** The resolved config for the dialog currently being rendered. */
235
- declare const NGX_DIALOG_DEFAULT_CONFIG: NgxDialogConfig<any>;
236
235
  declare const NGX_DIALOG_CONFIG: InjectionToken<NgxDialogConfig<any>>;
237
236
  /** The arbitrary content component passed to `open()`, projected via `*ngComponentOutlet`. */
238
237
  declare const DIALOG_CONTENT: InjectionToken<Type<any>>;
239
238
 
240
- export { DIALOG_CONTENT, DIALOG_DATA, DIALOG_REF, Dialog, NGX_DIALOG_CONFIG, NGX_DIALOG_DEFAULT_CONFIG, NgxDialogBody, NgxDialogComponent, NgxDialogConfig, NgxDialogFooter, NgxDialogHeader, NgxDialogModule, NgxDialogService, provideNgxDialog };
239
+ export { DIALOG_CONTENT, DIALOG_DATA, DIALOG_REF, Dialog, NGX_DIALOG_CONFIG, NgxDialogBody, NgxDialogComponent, NgxDialogConfig, NgxDialogFooter, NgxDialogHeader, NgxDialogModule, NgxDialogService, provideNgxDialog };
241
240
  export type { NgxDialogFooterAlign, NgxDialogFooterConfig, NgxDialogHeaderConfig, NgxDialogSize };