ngx-kit 7.3.2 → 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.
- package/fesm2022/ngx-kit-box-shadow.mjs +2 -2
- package/fesm2022/ngx-kit-box-shadow.mjs.map +1 -1
- package/fesm2022/ngx-kit-date-picker.mjs +27 -22
- package/fesm2022/ngx-kit-date-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-dialog.mjs +6 -5
- package/fesm2022/ngx-kit-dialog.mjs.map +1 -1
- package/fesm2022/ngx-kit-dropzone.mjs +5 -5
- package/fesm2022/ngx-kit-dropzone.mjs.map +1 -1
- package/fesm2022/ngx-kit-image-editor.mjs +14 -26
- package/fesm2022/ngx-kit-image-editor.mjs.map +1 -1
- package/fesm2022/ngx-kit-media-control-mse-media-engine-CtP97cb9.mjs +326 -0
- package/fesm2022/ngx-kit-media-control-mse-media-engine-CtP97cb9.mjs.map +1 -0
- package/fesm2022/ngx-kit-media-control.mjs +1847 -0
- package/fesm2022/ngx-kit-media-control.mjs.map +1 -0
- package/media-control/README.md +349 -0
- package/package.json +5 -1
- package/types/ngx-kit-date-picker.d.ts +1 -1
- package/types/ngx-kit-dialog.d.ts +1 -2
- package/types/ngx-kit-dropzone.d.ts +4 -4
- package/types/ngx-kit-image-editor.d.ts +10 -27
- package/types/ngx-kit-media-control.d.ts +671 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import * as ngx_kit_media_control from 'ngx-kit/media-control';
|
|
2
|
+
import * as _angular_core from '@angular/core';
|
|
3
|
+
import { Signal, ElementRef, InjectionToken, EnvironmentProviders, Type } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A single playable media item.
|
|
7
|
+
*
|
|
8
|
+
* `src` intentionally accepts the shapes a consumer already has lying around:
|
|
9
|
+
* a remote/relative URL string, a `Blob`/`File` (turned into an object URL
|
|
10
|
+
* internally and revoked on teardown), or a live `MediaStream`.
|
|
11
|
+
*/
|
|
12
|
+
interface NgxMediaSource {
|
|
13
|
+
/** Playback source. */
|
|
14
|
+
src: string | Blob | File | MediaStream;
|
|
15
|
+
/** MIME type, e.g. `audio/mpeg`, `video/mp4`. Helps capability detection avoid guessing from the extension. */
|
|
16
|
+
type?: string;
|
|
17
|
+
/** Optional codec string per RFC 6381, e.g. `avc1.42E01E,mp4a.40.2`. */
|
|
18
|
+
codecs?: string;
|
|
19
|
+
/** Display metadata, also used for Media Session integration. */
|
|
20
|
+
title?: string;
|
|
21
|
+
artist?: string;
|
|
22
|
+
album?: string;
|
|
23
|
+
/** Poster image for video. */
|
|
24
|
+
poster?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Separate download target. If omitted, falls back to `src` (only when
|
|
27
|
+
* `src` is a string — Blob/File/MediaStream have no meaningful download URL
|
|
28
|
+
* unless one is provided here).
|
|
29
|
+
*/
|
|
30
|
+
downloadUrl?: string;
|
|
31
|
+
/** Suggested file name for the download action. */
|
|
32
|
+
downloadFileName?: string;
|
|
33
|
+
/** Headers to send when the engine has to fetch this source itself (e.g. duration probing, WASM decoding). */
|
|
34
|
+
requestHeaders?: HeadersInit;
|
|
35
|
+
}
|
|
36
|
+
/** A queue of sources plus the active index. Replaces the old bare `PlayList[]`. */
|
|
37
|
+
interface NgxMediaPlaylist {
|
|
38
|
+
items: NgxMediaSource[];
|
|
39
|
+
currentIndex: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated Use {@link NgxMediaSource} instead. Kept so existing `PlayList[]`
|
|
43
|
+
* consumers keep compiling; `fileAddress`/`title` map 1:1 onto `src`/`title`.
|
|
44
|
+
*/
|
|
45
|
+
declare class PlayList {
|
|
46
|
+
title: string;
|
|
47
|
+
fileAddress: string;
|
|
48
|
+
}
|
|
49
|
+
declare function playListToMediaSource(item: PlayList): NgxMediaSource;
|
|
50
|
+
/**
|
|
51
|
+
* Content equality for `NgxMediaSource`/playlist arrays, not reference
|
|
52
|
+
* equality. `src` strings compare by value; `Blob`/`File`/`MediaStream`
|
|
53
|
+
* compare by identity (they can't be compared by content cheaply/safely).
|
|
54
|
+
*
|
|
55
|
+
* Every field that can change *downstream behavior* is compared —
|
|
56
|
+
* `artist`/`album`/`poster` feed Media Session metadata, `downloadUrl`/
|
|
57
|
+
* `downloadFileName` change what the download button actually does, and
|
|
58
|
+
* `requestHeaders` changes what bytes a server returns for the same URL.
|
|
59
|
+
* Fields that are purely cosmetic and never drive a decision (there
|
|
60
|
+
* currently are none — if you add one, document why it's excluded here).
|
|
61
|
+
*
|
|
62
|
+
* This exists specifically so a consumer that re-creates an equivalent
|
|
63
|
+
* array/object literal on every change-detection cycle (`[sources]="[{...}]"`,
|
|
64
|
+
* or a `.map()` derived from an otherwise-stable list) doesn't cause the
|
|
65
|
+
* player to treat it as "a new playlist" and reload media on every tick —
|
|
66
|
+
* see `computed(..., { equal: mediaSourcesEqual })` in `NgxMediaControl` and
|
|
67
|
+
* the same guard in `NgxMediaFacade.setPlaylist`.
|
|
68
|
+
*/
|
|
69
|
+
declare function mediaSourcesEqual(a: readonly NgxMediaSource[], b: readonly NgxMediaSource[]): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Derives a display file name from a source, tolerating everything a real
|
|
72
|
+
* URL can throw at `decodeURIComponent` (malformed `%` sequences, stray
|
|
73
|
+
* `#`/`?`) instead of letting the player crash just because it tried to
|
|
74
|
+
* make a pretty label. Never throws.
|
|
75
|
+
*/
|
|
76
|
+
declare function extractMediaFileName(source: Pick<NgxMediaSource, 'src' | 'title'>): string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Exhaustive playback state machine. Only one state is active at a time;
|
|
80
|
+
* transient conditions (buffering while playing, seeking while paused) are
|
|
81
|
+
* modeled as their own states rather than boolean flags layered on top of
|
|
82
|
+
* `playing`/`paused`, so `playing + destroyed` etc. cannot occur.
|
|
83
|
+
*/
|
|
84
|
+
type NgxMediaPlaybackState = 'idle' | 'loading' | 'ready' | 'playing' | 'paused' | 'buffering' | 'seeking' | 'ended' | 'error' | 'destroyed';
|
|
85
|
+
interface NgxTimeRange {
|
|
86
|
+
start: number;
|
|
87
|
+
end: number;
|
|
88
|
+
}
|
|
89
|
+
/** Read-only snapshot of engine state, exposed to the UI as a signal. */
|
|
90
|
+
interface NgxMediaState {
|
|
91
|
+
playback: NgxMediaPlaybackState;
|
|
92
|
+
currentTime: number;
|
|
93
|
+
duration: number;
|
|
94
|
+
buffered: readonly NgxTimeRange[];
|
|
95
|
+
volume: number;
|
|
96
|
+
muted: boolean;
|
|
97
|
+
playbackRate: number;
|
|
98
|
+
/** Only meaningful for video engines; undefined for audio-only. */
|
|
99
|
+
videoWidth?: number;
|
|
100
|
+
videoHeight?: number;
|
|
101
|
+
}
|
|
102
|
+
declare function initialMediaState(): NgxMediaState;
|
|
103
|
+
/** Guards against illegal transitions (e.g. anything -> playing after destroyed). */
|
|
104
|
+
declare function canTransition(from: NgxMediaPlaybackState, to: NgxMediaPlaybackState): boolean;
|
|
105
|
+
|
|
106
|
+
type NgxMediaKind = 'audio' | 'video';
|
|
107
|
+
/**
|
|
108
|
+
* Stable contract the UI/facade programs against. The UI never knows whether
|
|
109
|
+
* playback is happening through a native `<audio>`/`<video>` element, MSE, or
|
|
110
|
+
* a WASM decoder writing into an AudioBuffer — it only sees this interface.
|
|
111
|
+
*/
|
|
112
|
+
interface NgxMediaEngine {
|
|
113
|
+
readonly id: string;
|
|
114
|
+
readonly kind: NgxMediaKind;
|
|
115
|
+
/** Reactive, read-only state snapshot. Engines update this via an internal writable signal. */
|
|
116
|
+
readonly state: Signal<NgxMediaState>;
|
|
117
|
+
/**
|
|
118
|
+
* The element the UI should attach to the DOM (for video: for sizing/fullscreen/PiP;
|
|
119
|
+
* for audio: usually detached, kept alive only to drive playback).
|
|
120
|
+
* Null for engines that don't render through a media element (rare; e.g. a pure
|
|
121
|
+
* Web-Audio-buffer engine).
|
|
122
|
+
*/
|
|
123
|
+
readonly mediaElement: HTMLMediaElement | null;
|
|
124
|
+
load(source: NgxMediaSource, signal?: AbortSignal): Promise<void>;
|
|
125
|
+
play(): Promise<void>;
|
|
126
|
+
pause(): void;
|
|
127
|
+
seek(time: number): Promise<void> | void;
|
|
128
|
+
setVolume(volume: number): void;
|
|
129
|
+
setMuted(muted: boolean): void;
|
|
130
|
+
setPlaybackRate(rate: number): void;
|
|
131
|
+
destroy(): void;
|
|
132
|
+
}
|
|
133
|
+
/** Given a source, can this engine play it without further help? Cheap, synchronous-preferred check. */
|
|
134
|
+
interface NgxEngineCapability {
|
|
135
|
+
canPlay(source: NgxMediaSource): 'probably' | 'maybe' | '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type NgxMediaErrorCategory = 'NETWORK' | 'UNSUPPORTED_FORMAT' | 'DECODER' | 'ABORTED' | 'AUTOPLAY_BLOCKED' | 'MEDIA' | 'PERMISSION' | 'BROWSER_UNSUPPORTED' | 'UNKNOWN';
|
|
139
|
+
declare class NgxMediaError extends Error {
|
|
140
|
+
readonly category: NgxMediaErrorCategory;
|
|
141
|
+
/** Original DOM/browser error, if any — kept for `console.error`/telemetry, not surfaced to templates by default. */
|
|
142
|
+
readonly cause?: unknown | undefined;
|
|
143
|
+
constructor(category: NgxMediaErrorCategory, message: string,
|
|
144
|
+
/** Original DOM/browser error, if any — kept for `console.error`/telemetry, not surfaced to templates by default. */
|
|
145
|
+
cause?: unknown | undefined);
|
|
146
|
+
static fromMediaElementError(el: HTMLMediaElement): NgxMediaError;
|
|
147
|
+
static autoplayBlocked(cause?: unknown): NgxMediaError;
|
|
148
|
+
static unsupported(message: string): NgxMediaError;
|
|
149
|
+
static noDecoder(source: string): NgxMediaError;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Optional processing graph: `<audio>/<video>` → MediaElementAudioSourceNode
|
|
154
|
+
* → gain → (optional analyser) → destination.
|
|
155
|
+
*
|
|
156
|
+
* Never constructed unless a consumer asks for gain/analyser/effects — plain
|
|
157
|
+
* playback never creates an `AudioContext`, respecting autoplay policy and
|
|
158
|
+
* avoiding the "one AudioContext per instance whether needed or not" issue
|
|
159
|
+
* in the original component.
|
|
160
|
+
*
|
|
161
|
+
* Autoplay note: this class never calls `resume()` on its own outside a
|
|
162
|
+
* user-triggered `play()`/`connect()` call — bypassing the suspended-state
|
|
163
|
+
* policy is explicitly out of scope.
|
|
164
|
+
*/
|
|
165
|
+
declare class NgxWebAudioGraph {
|
|
166
|
+
private readonly element;
|
|
167
|
+
private ctx;
|
|
168
|
+
private sourceNode;
|
|
169
|
+
private gainNode;
|
|
170
|
+
private analyserNode;
|
|
171
|
+
constructor(element: HTMLMediaElement);
|
|
172
|
+
/** Idempotent — safe to call multiple times; only the first call does any work. */
|
|
173
|
+
ensureConnected(): {
|
|
174
|
+
context: AudioContext;
|
|
175
|
+
gain: GainNode;
|
|
176
|
+
};
|
|
177
|
+
setGain(value: number): void;
|
|
178
|
+
getAnalyser(fftSize?: number): AnalyserNode;
|
|
179
|
+
resumeOnUserGesture(): Promise<void>;
|
|
180
|
+
destroy(): void;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Everything the UI component needs, expressed as signals it can bind to
|
|
185
|
+
* directly, plus imperative commands. This is the only class
|
|
186
|
+
* `NgxMediaControl` talks to — it never touches an engine, a decoder, or the
|
|
187
|
+
* DOM media element itself. Swapping native/MSE/WASM playback never touches
|
|
188
|
+
* the component.
|
|
189
|
+
*/
|
|
190
|
+
declare class NgxMediaFacade {
|
|
191
|
+
private readonly zone;
|
|
192
|
+
private readonly engineFactory;
|
|
193
|
+
private readonly decoderRegistry;
|
|
194
|
+
private readonly destroyRef;
|
|
195
|
+
private readonly mediaSession;
|
|
196
|
+
private webAudio;
|
|
197
|
+
private loadAbort;
|
|
198
|
+
private kind;
|
|
199
|
+
private preload;
|
|
200
|
+
/**
|
|
201
|
+
* Belt-and-suspenders alongside `AbortController`: not every operation a
|
|
202
|
+
* superseded load might be mid-way through is actually cancellable (a
|
|
203
|
+
* decoder's own internal work, a promise someone else is holding a
|
|
204
|
+
* reference to, ...). Every `loadCurrent()` call captures the generation
|
|
205
|
+
* counter at its start and checks it again before committing any result —
|
|
206
|
+
* a stale generation means "a newer load has already started", full stop,
|
|
207
|
+
* regardless of whether the stale one's `AbortSignal` was actually honored
|
|
208
|
+
* by everything downstream.
|
|
209
|
+
*/
|
|
210
|
+
private loadGeneration;
|
|
211
|
+
private readonly _playlist;
|
|
212
|
+
private readonly _currentIndex;
|
|
213
|
+
/** Holds the live engine instance; swapped whenever a new source loads. */
|
|
214
|
+
private readonly _engine;
|
|
215
|
+
private get engine();
|
|
216
|
+
private readonly _error;
|
|
217
|
+
readonly playlist: _angular_core.Signal<NgxMediaSource[]>;
|
|
218
|
+
readonly currentIndex: _angular_core.Signal<number>;
|
|
219
|
+
/**
|
|
220
|
+
* Reads through to the current engine's own state signal, so every native
|
|
221
|
+
* media event (timeupdate, progress, ...) that the engine writes flows
|
|
222
|
+
* straight through here with no polling, no manual refresh, and no risk of
|
|
223
|
+
* going stale across an engine swap (native -> WASM fallback, etc.).
|
|
224
|
+
*
|
|
225
|
+
* When there is NO engine (playlist empty, or the engine was torn down
|
|
226
|
+
* after a load failure) but `error()` is set, this reports `playback:
|
|
227
|
+
* 'error'` rather than falling through to `idle` — otherwise `state()` and
|
|
228
|
+
* `error()` would be two sources of truth that can disagree, and anything
|
|
229
|
+
* built on `state().playback === 'error'` would silently miss real errors.
|
|
230
|
+
*/
|
|
231
|
+
readonly state: _angular_core.Signal<NgxMediaState>;
|
|
232
|
+
readonly error: _angular_core.Signal<NgxMediaError | null>;
|
|
233
|
+
readonly currentSource: _angular_core.Signal<NgxMediaSource>;
|
|
234
|
+
readonly mediaElement: _angular_core.Signal<HTMLMediaElement | null>;
|
|
235
|
+
constructor();
|
|
236
|
+
/** Deterministic on a kind change: if a source is already active, immediately rebuilds the engine as the new kind (audio <-> video element) instead of leaving a stale-kind engine behind a UI that now expects the other one. */
|
|
237
|
+
configure(kind: NgxMediaKind): void;
|
|
238
|
+
/** Applies to loads started after this call — not retroactive to an element already created. */
|
|
239
|
+
setPreload(preload: 'none' | 'metadata' | 'auto'): void;
|
|
240
|
+
setPlaylist(sources: NgxMediaSource[], startIndex?: number, autoLoad?: boolean): void;
|
|
241
|
+
loadCurrent(playAfter?: boolean): Promise<void>;
|
|
242
|
+
/** Forces the current track to actually reload from scratch — e.g. a "retry after error" action — bypassing the engine's own idempotent-load skip (which is otherwise correct: selecting the same, already-loaded track should NOT re-fetch it). */
|
|
243
|
+
reloadCurrent(): Promise<void>;
|
|
244
|
+
/**
|
|
245
|
+
* Tries the native engine first (the normal fast path). Falls back to the
|
|
246
|
+
* decoder registry ONLY when the failure category indicates the format
|
|
247
|
+
* itself is the problem (`UNSUPPORTED_FORMAT`, `DECODER`) — never for
|
|
248
|
+
* `NETWORK` (404/500/CORS/interrupted), `ABORTED`, or `PERMISSION`, where
|
|
249
|
+
* retrying via a decoder would just repeat the same failing request
|
|
250
|
+
* (or a fresh one to the same broken URL) for no benefit. This matters
|
|
251
|
+
* because `canPlayType()`/extension/mime are only ever a hint and can be
|
|
252
|
+
* wrong (a mislabeled or fake extension), so the decision to fall back
|
|
253
|
+
* has to be made from the real failure, not guessed upfront.
|
|
254
|
+
*/
|
|
255
|
+
private resolveEngine;
|
|
256
|
+
private shouldFallbackToDecoder;
|
|
257
|
+
play(): Promise<void>;
|
|
258
|
+
pause(): void;
|
|
259
|
+
togglePlayPause(): void;
|
|
260
|
+
seek(time: number): void;
|
|
261
|
+
skip(deltaSeconds: number): void;
|
|
262
|
+
setVolume(volume: number): void;
|
|
263
|
+
setMuted(muted: boolean): void;
|
|
264
|
+
setPlaybackRate(rate: number): void;
|
|
265
|
+
previous(): Promise<void>;
|
|
266
|
+
next(): Promise<void>;
|
|
267
|
+
selectIndex(index: number): Promise<void>;
|
|
268
|
+
/** Lazily creates the Web Audio graph — never touched unless a consumer asks for it. Always tied to the CURRENT engine's element; invalidated automatically on every engine swap (see `loadCurrent`). */
|
|
269
|
+
getWebAudioGraph(): NgxWebAudioGraph;
|
|
270
|
+
private bindMediaSession;
|
|
271
|
+
destroy(): void;
|
|
272
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgxMediaFacade, never>;
|
|
273
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<NgxMediaFacade>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
type NgxMediaType = 'audio' | 'video' | 'both';
|
|
277
|
+
declare class NgxMediaControl {
|
|
278
|
+
/**
|
|
279
|
+
* `'audio'` renders an audio-style control surface (no video frame).
|
|
280
|
+
* `'video'` renders a video frame with fullscreen/PiP controls.
|
|
281
|
+
* `'both'` renders the video frame but degrades gracefully to audio-only
|
|
282
|
+
* controls when the active source has no video track.
|
|
283
|
+
*/
|
|
284
|
+
readonly type: _angular_core.InputSignal<NgxMediaType>;
|
|
285
|
+
/**
|
|
286
|
+
* Poster precedence: this input, when set, ALWAYS wins over a per-source
|
|
287
|
+
* `poster` (useful for a fixed placeholder while sources vary). Leave it
|
|
288
|
+
* unset to let each `NgxMediaSource.poster` take effect per track, and
|
|
289
|
+
* clearing either one correctly clears the poster actually applied (see
|
|
290
|
+
* the mount effect below) instead of leaving a stale image behind.
|
|
291
|
+
*/
|
|
292
|
+
readonly poster: _angular_core.InputSignal<string | undefined>;
|
|
293
|
+
readonly showList: _angular_core.InputSignal<boolean>;
|
|
294
|
+
readonly download: _angular_core.InputSignal<boolean>;
|
|
295
|
+
readonly showFileName: _angular_core.InputSignal<boolean>;
|
|
296
|
+
readonly showSpeed: _angular_core.InputSignal<boolean>;
|
|
297
|
+
readonly showVolume: _angular_core.InputSignal<boolean>;
|
|
298
|
+
readonly linear: _angular_core.InputSignal<boolean>;
|
|
299
|
+
readonly preload: _angular_core.InputSignal<"none" | "metadata" | "auto">;
|
|
300
|
+
/**
|
|
301
|
+
* IMPORTANT, and different from earlier versions of this component: a
|
|
302
|
+
* native `<audio>`/`<video src>` fetch cannot carry arbitrary request
|
|
303
|
+
* headers — that's a browser platform limitation, not something this
|
|
304
|
+
* library can paper over without fetching the whole file into a `Blob`
|
|
305
|
+
* first (which defeats streaming/range-requests for anything but small
|
|
306
|
+
* clips, and was explicitly the wrong default per the library's
|
|
307
|
+
* performance goals). So `fetchHeaders` is applied as the DEFAULT
|
|
308
|
+
* `requestHeaders` for any resolved source that doesn't specify its own
|
|
309
|
+
* (see `resolvedSources` below) — it takes effect for sources that end
|
|
310
|
+
* up on a decoder/MSE backend (which do fetch through
|
|
311
|
+
* `NGX_MEDIA_REQUEST_HANDLER` and can carry headers), and has no effect
|
|
312
|
+
* on plain natively-playable sources, which never go through a fetch at
|
|
313
|
+
* all. Set headers per-source via `NgxMediaSource.requestHeaders`
|
|
314
|
+
* whenever different tracks need different auth.
|
|
315
|
+
*/
|
|
316
|
+
readonly fetchHeaders: _angular_core.InputSignal<HeadersInit | undefined>;
|
|
317
|
+
/** Legacy simple form: bare URLs, one per playlist entry. Prefer `sources` for titles/artwork/per-track headers. */
|
|
318
|
+
readonly fileList: _angular_core.InputSignal<string[] | undefined>;
|
|
319
|
+
/** Preferred over `fileList`: full `NgxMediaSource` objects. */
|
|
320
|
+
readonly sources: _angular_core.InputSignal<NgxMediaSource[] | undefined>;
|
|
321
|
+
readonly trackChange: _angular_core.OutputEmitterRef<NgxMediaSource>;
|
|
322
|
+
readonly error: _angular_core.OutputEmitterRef<string>;
|
|
323
|
+
private readonly doc;
|
|
324
|
+
private readonly destroyRef;
|
|
325
|
+
protected readonly facade: NgxMediaFacade;
|
|
326
|
+
protected readonly containerRef: _angular_core.Signal<ElementRef<HTMLElement>>;
|
|
327
|
+
protected readonly videoHostRef: _angular_core.Signal<ElementRef<HTMLElement> | undefined>;
|
|
328
|
+
protected readonly downloading: _angular_core.WritableSignal<boolean>;
|
|
329
|
+
protected readonly togglePlayList: _angular_core.WritableSignal<boolean>;
|
|
330
|
+
protected readonly isFullscreen: _angular_core.WritableSignal<boolean>;
|
|
331
|
+
/** Local seek preview — see the seek-slider handlers below for why the slider never binds `state().currentTime` directly while the user is dragging it. */
|
|
332
|
+
protected readonly isSeeking: _angular_core.WritableSignal<boolean>;
|
|
333
|
+
protected readonly seekPreviewTime: _angular_core.WritableSignal<number>;
|
|
334
|
+
protected readonly state: _angular_core.Signal<ngx_kit_media_control.NgxMediaState>;
|
|
335
|
+
protected readonly playlist: _angular_core.Signal<NgxMediaSource[]>;
|
|
336
|
+
protected readonly currentIndex: _angular_core.Signal<number>;
|
|
337
|
+
protected readonly currentSource: _angular_core.Signal<NgxMediaSource>;
|
|
338
|
+
protected readonly playerError: _angular_core.Signal<ngx_kit_media_control.NgxMediaError | null>;
|
|
339
|
+
protected readonly effectiveKind: _angular_core.Signal<"audio" | "video">;
|
|
340
|
+
protected readonly displaySeekTime: _angular_core.Signal<number>;
|
|
341
|
+
protected readonly currentTimeDisplay: _angular_core.Signal<string>;
|
|
342
|
+
protected readonly totalTimeDisplay: _angular_core.Signal<string>;
|
|
343
|
+
protected readonly speedDisplay: _angular_core.Signal<string>;
|
|
344
|
+
protected readonly fileName: _angular_core.Signal<string>;
|
|
345
|
+
protected readonly isPlaying: _angular_core.Signal<boolean>;
|
|
346
|
+
protected readonly isBuffering: _angular_core.Signal<boolean>;
|
|
347
|
+
protected readonly hasLoadError: _angular_core.Signal<boolean>;
|
|
348
|
+
protected readonly errorMessage: _angular_core.Signal<string>;
|
|
349
|
+
protected readonly canFullscreen: _angular_core.Signal<boolean>;
|
|
350
|
+
protected readonly canPip: _angular_core.Signal<boolean>;
|
|
351
|
+
/**
|
|
352
|
+
* Derived from `sources()` OR `fileList()` (whichever the consumer used),
|
|
353
|
+
* memoized by CONTENT rather than reference (`equal: mediaSourcesEqual`).
|
|
354
|
+
* Angular's `input()` signal only compares by reference — a parent that
|
|
355
|
+
* binds an inline literal, e.g. `[sources]="[{...}]"`, or otherwise
|
|
356
|
+
* re-derives an equivalent array on every change-detection pass, produces
|
|
357
|
+
* a *new* array every tick. Without the custom `equal`, the effect below
|
|
358
|
+
* would see "a new value" on every tick and reload media in a loop. A
|
|
359
|
+
* content-equal `computed` keeps returning its previous, `===`-stable
|
|
360
|
+
* value whenever the new content is equivalent, so the effect below
|
|
361
|
+
* simply doesn't re-run.
|
|
362
|
+
*/
|
|
363
|
+
protected readonly resolvedSources: _angular_core.Signal<NgxMediaSource[]>;
|
|
364
|
+
/** Tracks what's actually mounted in `videoHostRef`, so the mount effect only touches the DOM (and re-registers listeners) when the element itself changes, not on every unrelated signal read. */
|
|
365
|
+
private mountedElement;
|
|
366
|
+
private dblClickHandler;
|
|
367
|
+
private downloadResetTimeout;
|
|
368
|
+
constructor();
|
|
369
|
+
private detachMountedElement;
|
|
370
|
+
playPause(): void;
|
|
371
|
+
play(): void;
|
|
372
|
+
stop(): void;
|
|
373
|
+
muteUnmute(): void;
|
|
374
|
+
/**
|
|
375
|
+
* Seek strategy: dragging the slider only updates a local preview
|
|
376
|
+
* (`seekPreviewTime`) — no `facade.seek()` call, so no HTTP range
|
|
377
|
+
* request or media-pipeline seek happens per pixel of drag. The actual
|
|
378
|
+
* seek fires once, on `change` (`onSeekCommit`), which browsers fire
|
|
379
|
+
* both on pointer-release-after-drag AND after each discrete keyboard
|
|
380
|
+
* adjustment — so keyboard accessibility (arrow keys) still seeks
|
|
381
|
+
* immediately per keypress, it's only continuous dragging that's
|
|
382
|
+
* coalesced into a single request.
|
|
383
|
+
*/
|
|
384
|
+
onSeekInput(ev: Event): void;
|
|
385
|
+
onSeekCommit(ev: Event): void;
|
|
386
|
+
skipTrack(seconds: number): void;
|
|
387
|
+
increaseSpeed(): void;
|
|
388
|
+
decreaseSpeed(): void;
|
|
389
|
+
changeVolume(ev: Event): void;
|
|
390
|
+
previous(): void;
|
|
391
|
+
next(): void;
|
|
392
|
+
onClickPlayList(index: number): void;
|
|
393
|
+
togglePlaylistPanel(): void;
|
|
394
|
+
/** Force-reload the current track from scratch (e.g. a "retry" affordance after an error) rather than relying on selecting the same playlist entry, which deliberately does NOT reload an already-loaded track. */
|
|
395
|
+
retryCurrent(): void;
|
|
396
|
+
downloadCurrentFile(): void;
|
|
397
|
+
toggleFullscreen(): Promise<void>;
|
|
398
|
+
togglePictureInPicture(): Promise<void>;
|
|
399
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgxMediaControl, never>;
|
|
400
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<NgxMediaControl, "ngx-media-control", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "poster": { "alias": "poster"; "required": false; "isSignal": true; }; "showList": { "alias": "showList"; "required": false; "isSignal": true; }; "download": { "alias": "download"; "required": false; "isSignal": true; }; "showFileName": { "alias": "showFileName"; "required": false; "isSignal": true; }; "showSpeed": { "alias": "showSpeed"; "required": false; "isSignal": true; }; "showVolume": { "alias": "showVolume"; "required": false; "isSignal": true; }; "linear": { "alias": "linear"; "required": false; "isSignal": true; }; "preload": { "alias": "preload"; "required": false; "isSignal": true; }; "fetchHeaders": { "alias": "fetchHeaders"; "required": false; "isSignal": true; }; "fileList": { "alias": "fileList"; "required": false; "isSignal": true; }; "sources": { "alias": "sources"; "required": false; "isSignal": true; }; }, { "trackChange": "trackChange"; "error": "error"; }, never, never, true, never>;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* A decoder plugin doesn't decode into a static buffer and hand it back —
|
|
405
|
+
* real media needs streaming, seeking and duration discovery, so a decoder's
|
|
406
|
+
* job is to produce a fully-formed {@link NgxMediaEngine} that the facade can
|
|
407
|
+
* drive identically to the native engine. This keeps the abstraction usable
|
|
408
|
+
* for both "decode once into an AudioBuffer" (short clips) and "feed a
|
|
409
|
+
* MediaSource/SourceBuffer progressively" (long/streamed media) strategies,
|
|
410
|
+
* which a flat `decode() -> ArrayBuffer` contract cannot support.
|
|
411
|
+
*/
|
|
412
|
+
interface NgxMediaDecoder {
|
|
413
|
+
/** Unique id, used for logging/priority/debugging. */
|
|
414
|
+
readonly id: string;
|
|
415
|
+
/** Formats this decoder claims, informational (used by the registry for a fast pre-filter). */
|
|
416
|
+
readonly formats: readonly string[];
|
|
417
|
+
/**
|
|
418
|
+
* Higher runs first when multiple decoders claim the same source.
|
|
419
|
+
* Default treated as 0 by the registry.
|
|
420
|
+
*/
|
|
421
|
+
readonly priority?: number;
|
|
422
|
+
/** Cheap capability check; may be async if it needs to sniff bytes. `signal` lets the registry cancel an in-flight probe (e.g. the user switched tracks before this decoder answered). */
|
|
423
|
+
canDecode(source: NgxMediaSource, signal?: AbortSignal): boolean | Promise<boolean>;
|
|
424
|
+
/**
|
|
425
|
+
* Build a ready-to-use engine for this source. `signal` allows the caller
|
|
426
|
+
* to cancel an in-flight WASM module load / stream open.
|
|
427
|
+
*/
|
|
428
|
+
createEngine(source: NgxMediaSource, signal?: AbortSignal): Promise<NgxMediaEngine>;
|
|
429
|
+
/** Release any module-level resources (worker pools, cached WASM instances) held by this decoder itself. */
|
|
430
|
+
destroy(): void;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Base class for WASM-backed decoders. It does the two things every such
|
|
435
|
+
* decoder needs regardless of which codec module it wraps:
|
|
436
|
+
*
|
|
437
|
+
* 1. Lazily `import()`s the codec module on first real use, in a Web Worker
|
|
438
|
+
* when a worker factory is supplied, so the (typically large) WASM binary
|
|
439
|
+
* never enters the main bundle or the main thread's JS heap unless a
|
|
440
|
+
* source actually needs it.
|
|
441
|
+
* 2. Provides a cancellable, disposable lifecycle (`AbortSignal`-aware load,
|
|
442
|
+
* `destroy()` terminates the worker / frees the WASM instance) that is
|
|
443
|
+
* safe under overlap: a `destroy()` that lands while `loadModule()` is
|
|
444
|
+
* still pending must not let that pending load "resurrect" a module
|
|
445
|
+
* reference for a decoder instance that's already gone, and must not
|
|
446
|
+
* let a subsequent `createEngine()` call kick off a SECOND concurrent
|
|
447
|
+
* module load while the first is still in flight.
|
|
448
|
+
*
|
|
449
|
+
* This class intentionally does NOT ship a real codec — bundling e.g.
|
|
450
|
+
* `@ffmpeg/ffmpeg` here would violate "no WASM in the default bundle" for
|
|
451
|
+
* every consumer, including ones who never touch this file. Concrete
|
|
452
|
+
* decoders (FLAC, WMA, a consumer's own 3GP decoder, ...) extend this and
|
|
453
|
+
* supply `loadModule` + `buildEngine`.
|
|
454
|
+
*/
|
|
455
|
+
declare abstract class NgxWasmDecoderBase implements NgxMediaDecoder {
|
|
456
|
+
abstract readonly id: string;
|
|
457
|
+
abstract readonly formats: readonly string[];
|
|
458
|
+
priority: number;
|
|
459
|
+
private worker;
|
|
460
|
+
private modulePromise;
|
|
461
|
+
/** Bumped by every `destroy()`; lets an in-flight `loadModule()`/`createEngine()` notice it's talking to a dead instance once it resolves. */
|
|
462
|
+
private generation;
|
|
463
|
+
/** Import path/factory for the codec's WASM glue module. Not evaluated until first `canDecode()`/`createEngine()` call. */
|
|
464
|
+
protected abstract loadModule(): Promise<unknown>;
|
|
465
|
+
/** Optional: return a `Worker` to run decoding off the main thread. Omit for short clips where worker overhead isn't worth it. */
|
|
466
|
+
protected createWorker?(): Worker;
|
|
467
|
+
abstract canDecode(source: NgxMediaSource, signal?: AbortSignal): boolean | Promise<boolean>;
|
|
468
|
+
/**
|
|
469
|
+
* Subclasses implement the actual decode-to-playable-engine step once the
|
|
470
|
+
* module (and optionally worker) are ready. Typical strategies:
|
|
471
|
+
* - decode fully into an `AudioBuffer`, play via Web Audio (`AudioBufferSourceNode`)
|
|
472
|
+
* - decode progressively, feed an MSE `SourceBuffer`
|
|
473
|
+
*/
|
|
474
|
+
protected abstract buildEngine(source: NgxMediaSource, module: unknown, worker: Worker | null, signal?: AbortSignal): Promise<NgxMediaEngine>;
|
|
475
|
+
createEngine(source: NgxMediaSource, signal?: AbortSignal): Promise<NgxMediaEngine>;
|
|
476
|
+
private ensureModuleLoaded;
|
|
477
|
+
destroy(): void;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Minimal playable-engine wrapper around a fully-decoded `AudioBuffer`,
|
|
481
|
+
* useful for WASM decoders that decode short clips completely up front
|
|
482
|
+
* rather than streaming. Reuses the browser's own `AudioContext` playback
|
|
483
|
+
* graph instead of a native `<audio>` element, since there's no compressed
|
|
484
|
+
* file for the browser to play directly anymore.
|
|
485
|
+
*
|
|
486
|
+
* Playback-position bookkeeping: `offset` is the buffer-time position when
|
|
487
|
+
* the current `AudioBufferSourceNode` was (re)started, `startedAt` is the
|
|
488
|
+
* `AudioContext.currentTime` at that moment, and `rate` is the playback
|
|
489
|
+
* rate in effect since then — so at any instant, position =
|
|
490
|
+
* `offset + (ctx.currentTime - startedAt) * rate`. Every operation that
|
|
491
|
+
* stops or restarts the source (pause, seek, rate change while playing,
|
|
492
|
+
* replay after ended) re-baselines these three fields together so that
|
|
493
|
+
* formula stays correct instead of drifting.
|
|
494
|
+
*/
|
|
495
|
+
declare class DecodedBufferEngine implements NgxMediaEngine {
|
|
496
|
+
private readonly buffer;
|
|
497
|
+
readonly id: string;
|
|
498
|
+
readonly kind: NgxMediaKind;
|
|
499
|
+
readonly mediaElement: null;
|
|
500
|
+
private readonly _state;
|
|
501
|
+
readonly state: Signal<NgxMediaState>;
|
|
502
|
+
private ctx;
|
|
503
|
+
private source;
|
|
504
|
+
private gain;
|
|
505
|
+
private startedAt;
|
|
506
|
+
private offset;
|
|
507
|
+
private rate;
|
|
508
|
+
private volume;
|
|
509
|
+
private muted;
|
|
510
|
+
private rafId;
|
|
511
|
+
/** Bumped every time a NEW `AudioBufferSourceNode` is created, so a stale `onended` from a node that was `.stop()`-ed (rather than actually finishing) can recognize it's obsolete and not overwrite newer state. */
|
|
512
|
+
private playToken;
|
|
513
|
+
constructor(decoderId: string, ctx: AudioContext, buffer: AudioBuffer);
|
|
514
|
+
play(): Promise<void>;
|
|
515
|
+
pause(): void;
|
|
516
|
+
seek(time: number): void;
|
|
517
|
+
setVolume(volume: number): void;
|
|
518
|
+
setMuted(muted: boolean): void;
|
|
519
|
+
setPlaybackRate(rate: number): void;
|
|
520
|
+
load(): Promise<void>;
|
|
521
|
+
destroy(): void;
|
|
522
|
+
private currentPosition;
|
|
523
|
+
/** Drives `currentTime` for the UI while actually playing — never runs during pause/ended/destroyed, so there's no animation loop burning frames when nothing is moving. */
|
|
524
|
+
private startTimeTracking;
|
|
525
|
+
private stopTimeTracking;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Default fast-path engine: plays through a real `<audio>`/`<video>` element.
|
|
530
|
+
* Zero WASM, zero MediaSource — the browser's own decode pipeline does the work.
|
|
531
|
+
*
|
|
532
|
+
* Load semantics (the part that matters most for network behavior):
|
|
533
|
+
* - `load()` is a real 3-state machine (idle/loading/ready) keyed on
|
|
534
|
+
* {@link SourceIdentity}, not just a `readyState` snapshot:
|
|
535
|
+
* same source + currently loading -> returns the SAME in-flight promise
|
|
536
|
+
* same source + already loaded -> resolves immediately, no `el.load()` call
|
|
537
|
+
* different source -> proceeds; the browser's own load
|
|
538
|
+
* algorithm aborts whatever the
|
|
539
|
+
* element was doing before
|
|
540
|
+
* - Setting `el.src` already queues the browser's media load algorithm;
|
|
541
|
+
* the immediate `el.load()` call right after does not cause a second
|
|
542
|
+
* network request — its purpose is to make the abort-then-restart
|
|
543
|
+
* synchronous (needed for older WebKit), not to "load twice". The state
|
|
544
|
+
* machine above is what actually prevents redundant fetches across
|
|
545
|
+
* repeated `load()` calls for the same source.
|
|
546
|
+
*
|
|
547
|
+
* Performance notes:
|
|
548
|
+
* - `timeupdate`/`progress`/etc. fire far more often than the UI needs to
|
|
549
|
+
* re-render; listeners run via `runOutsideAngular`, and `patch()` skips
|
|
550
|
+
* the signal write entirely when nothing actually changed (including a
|
|
551
|
+
* content-equal `buffered` array, which would otherwise allocate and
|
|
552
|
+
* write on every `progress` tick even when the ranges didn't move).
|
|
553
|
+
*/
|
|
554
|
+
declare class NativeMediaEngine implements NgxMediaEngine {
|
|
555
|
+
/** Provided by the facade so listener callbacks never re-enter Angular's zone. */
|
|
556
|
+
private readonly runOutsideAngular;
|
|
557
|
+
readonly id = "native";
|
|
558
|
+
readonly kind: NgxMediaKind;
|
|
559
|
+
private readonly _state;
|
|
560
|
+
readonly state: Signal<NgxMediaState>;
|
|
561
|
+
private readonly el;
|
|
562
|
+
private objectUrl;
|
|
563
|
+
private destroyed;
|
|
564
|
+
private readonly cleanupFns;
|
|
565
|
+
private loadState;
|
|
566
|
+
private loadedIdentity;
|
|
567
|
+
private pendingLoad;
|
|
568
|
+
private activeLoadController;
|
|
569
|
+
constructor(kind: NgxMediaKind,
|
|
570
|
+
/** Provided by the facade so listener callbacks never re-enter Angular's zone. */
|
|
571
|
+
runOutsideAngular?: <T>(fn: () => T) => T, existingElement?: HTMLMediaElement, preload?: 'none' | 'metadata' | 'auto');
|
|
572
|
+
get mediaElement(): HTMLMediaElement;
|
|
573
|
+
load(source: NgxMediaSource, abort?: AbortSignal): Promise<void>;
|
|
574
|
+
private performLoad;
|
|
575
|
+
play(): Promise<void>;
|
|
576
|
+
pause(): void;
|
|
577
|
+
seek(time: number): void;
|
|
578
|
+
setVolume(volume: number): void;
|
|
579
|
+
setMuted(muted: boolean): void;
|
|
580
|
+
setPlaybackRate(rate: number): void;
|
|
581
|
+
destroy(): void;
|
|
582
|
+
private resolveSrc;
|
|
583
|
+
private releaseObjectUrl;
|
|
584
|
+
/** Skips the signal write entirely when every field in `partial` is already equal to current state. */
|
|
585
|
+
private patch;
|
|
586
|
+
private bufferedRanges;
|
|
587
|
+
private attachListeners;
|
|
588
|
+
/**
|
|
589
|
+
* Some servers/streams report `duration === Infinity` until more data
|
|
590
|
+
* arrives. This forces the browser to resolve it immediately by seeking
|
|
591
|
+
* near the end and back — which works, but each call costs 1-2 extra
|
|
592
|
+
* HTTP range requests. No longer called automatically; opt-in for the
|
|
593
|
+
* rare case where you genuinely need duration *immediately* rather than
|
|
594
|
+
* letting `durationchange` update it naturally during normal playback.
|
|
595
|
+
*/
|
|
596
|
+
probeUnseekableDuration(): Promise<void>;
|
|
597
|
+
private assertNotDestroyed;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Multi-provider token: every decoder a consumer registers via
|
|
602
|
+
* `provideNgxMediaDecoder()` is picked up automatically by the registry.
|
|
603
|
+
*
|
|
604
|
+
* IMPORTANT: this token intentionally has NO default factory. Angular does
|
|
605
|
+
* not allow mixing a token's own default provider with externally
|
|
606
|
+
* registered `multi: true` providers for the SAME token — doing so throws
|
|
607
|
+
* at DI-construction time the moment a consumer actually registers a
|
|
608
|
+
* decoder (a real, if latent, bug this fixes). The correct pattern for an
|
|
609
|
+
* "optionally empty" multi token is `@Optional()` injection at the
|
|
610
|
+
* consumption site (see `NgxMediaDecoderRegistry`), which yields `null`
|
|
611
|
+
* when nothing has registered and an array when one or more have.
|
|
612
|
+
*/
|
|
613
|
+
declare const NGX_MEDIA_DECODER: InjectionToken<NgxMediaDecoder>;
|
|
614
|
+
/** Pluggable network layer so auth/signed URLs don't require coupling the core to HttpClient. */
|
|
615
|
+
interface NgxMediaRequestHandler {
|
|
616
|
+
request(source: NgxMediaSource, signal?: AbortSignal): Promise<Response>;
|
|
617
|
+
}
|
|
618
|
+
declare const NGX_MEDIA_REQUEST_HANDLER: InjectionToken<NgxMediaRequestHandler>;
|
|
619
|
+
interface NgxMediaConfig {
|
|
620
|
+
/** Default preload hint for native engines. */
|
|
621
|
+
preload: 'none' | 'metadata' | 'auto';
|
|
622
|
+
/** Ceiling for the number of decoder `canDecode` probes run in parallel. */
|
|
623
|
+
maxConcurrentDecoderProbes: number;
|
|
624
|
+
}
|
|
625
|
+
declare const NGX_MEDIA_CONFIG_DEFAULTS: NgxMediaConfig;
|
|
626
|
+
declare const NGX_MEDIA_CONFIG: InjectionToken<NgxMediaConfig>;
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Root-level setup: `bootstrapApplication(App, { providers: [provideNgxMedia({...})] })`.
|
|
630
|
+
* The supplied config is merged over the library's own defaults, not
|
|
631
|
+
* replacing them outright — `provideNgxMedia({ preload: 'auto' })` must not
|
|
632
|
+
* silently drop `maxConcurrentDecoderProbes` and leave it `undefined`.
|
|
633
|
+
*/
|
|
634
|
+
declare function provideNgxMedia(config?: Partial<NgxMediaConfig>): EnvironmentProviders;
|
|
635
|
+
/**
|
|
636
|
+
* Registers a decoder plugin into the `NGX_MEDIA_DECODER` multi-provider
|
|
637
|
+
* token. Accepts a class (instantiated via DI, so it can itself inject
|
|
638
|
+
* services) or a ready-made instance for simple cases.
|
|
639
|
+
*
|
|
640
|
+
* bootstrapApplication(AppComponent, {
|
|
641
|
+
* providers: [provideNgxMediaDecoder(My3gpDecoder)],
|
|
642
|
+
* });
|
|
643
|
+
*/
|
|
644
|
+
declare function provideNgxMediaDecoder(decoder: Type<NgxMediaDecoder> | NgxMediaDecoder): EnvironmentProviders;
|
|
645
|
+
/** Registers a custom network layer (auth headers, signed URLs, a non-fetch HTTP client, ...). */
|
|
646
|
+
declare function provideNgxMediaRequestHandler(handler: NgxMediaRequestHandler): EnvironmentProviders;
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Format time in seconds as MM:SS
|
|
650
|
+
* @param seconds number
|
|
651
|
+
* @returns MM:SS
|
|
652
|
+
*/
|
|
653
|
+
declare function formatTime(seconds: number): string;
|
|
654
|
+
|
|
655
|
+
/** True only in a real browser context — guards every browser API access for SSR. */
|
|
656
|
+
declare function isBrowser(): boolean;
|
|
657
|
+
declare function hasMediaSource(): boolean;
|
|
658
|
+
declare function hasAudioContext(): boolean;
|
|
659
|
+
declare function getAudioContextCtor(): typeof AudioContext | undefined;
|
|
660
|
+
declare function hasMediaSession(): boolean;
|
|
661
|
+
declare function hasPictureInPicture(): boolean;
|
|
662
|
+
declare function hasFullscreen(): boolean;
|
|
663
|
+
/**
|
|
664
|
+
* Best-effort container/codec detection using `canPlayType`. Never trusts the
|
|
665
|
+
* file extension alone: extension is only a last-resort hint when neither
|
|
666
|
+
* `type` nor `codecs` was supplied.
|
|
667
|
+
*/
|
|
668
|
+
declare function canPlayNatively(el: HTMLMediaElement, source: NgxMediaSource): 'probably' | 'maybe' | '';
|
|
669
|
+
|
|
670
|
+
export { DecodedBufferEngine, NGX_MEDIA_CONFIG, NGX_MEDIA_CONFIG_DEFAULTS, NGX_MEDIA_DECODER, NGX_MEDIA_REQUEST_HANDLER, NativeMediaEngine, NgxMediaControl, NgxMediaError, NgxWasmDecoderBase, PlayList, canPlayNatively, canTransition, extractMediaFileName, formatTime, getAudioContextCtor, hasAudioContext, hasFullscreen, hasMediaSession, hasMediaSource, hasPictureInPicture, initialMediaState, isBrowser, mediaSourcesEqual, playListToMediaSource, provideNgxMedia, provideNgxMediaDecoder, provideNgxMediaRequestHandler };
|
|
671
|
+
export type { NgxEngineCapability, NgxMediaConfig, NgxMediaDecoder, NgxMediaEngine, NgxMediaErrorCategory, NgxMediaKind, NgxMediaPlaybackState, NgxMediaPlaylist, NgxMediaRequestHandler, NgxMediaSource, NgxMediaState, NgxMediaType, NgxTimeRange };
|