@gcorevideo/player 2.28.36 → 2.30.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/README.md +108 -0
- package/assets/media-control/media-control.scss +8 -6
- package/assets/multi-camera/multicamera.ejs +27 -23
- package/assets/multi-camera/style.scss +7 -34
- package/assets/style/main.scss +2 -2
- package/dist/core.js +24 -7
- package/dist/index.css +324 -346
- package/dist/index.embed.js +24 -46
- package/dist/index.js +471 -245
- package/docs/api/player.md +22 -9
- package/docs/api/player.mediacontrol.setkeepvisible.md +56 -0
- package/docs/api/player.multicamera.md +0 -28
- package/docs/api/player.multiccamerasourceinfo.md +27 -0
- package/docs/api/{player.multicamera.unbindevents.md → player.multisourcesmode.md} +4 -7
- package/docs/api/player.sourcecontroller.md +0 -37
- package/lib/Player.d.ts +9 -0
- package/lib/Player.d.ts.map +1 -1
- package/lib/Player.js +11 -0
- package/lib/index.plugins.d.ts +1 -0
- package/lib/index.plugins.d.ts.map +1 -1
- package/lib/index.plugins.js +1 -0
- package/lib/playback/dash-playback/DashPlayback.d.ts +2 -1
- package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/playback/dash-playback/DashPlayback.js +5 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts +2 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/types.d.ts +9 -0
- package/lib/playback/types.d.ts.map +1 -1
- package/lib/playback.types.d.ts +0 -6
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/plugins/multi-camera/MultiCamera.d.ts +21 -4
- package/lib/plugins/multi-camera/MultiCamera.d.ts.map +1 -1
- package/lib/plugins/multi-camera/MultiCamera.js +70 -134
- package/lib/plugins/source-controller/SourceController.d.ts +0 -39
- package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
- package/lib/plugins/source-controller/SourceController.js +0 -39
- package/lib/plugins/token-refresh/TokenRefreshPlugin.d.ts +119 -0
- package/lib/plugins/token-refresh/TokenRefreshPlugin.d.ts.map +1 -0
- package/lib/plugins/token-refresh/TokenRefreshPlugin.js +318 -0
- package/lib/plugins/token-refresh/index.d.ts +2 -0
- package/lib/plugins/token-refresh/index.d.ts.map +1 -0
- package/lib/plugins/token-refresh/index.js +1 -0
- package/lib/utils/mediaSources.d.ts +4 -0
- package/lib/utils/mediaSources.d.ts.map +1 -1
- package/lib/utils/mediaSources.js +8 -6
- package/package.json +1 -1
- package/src/Player.ts +12 -0
- package/src/index.plugins.ts +1 -0
- package/src/playback/dash-playback/DashPlayback.ts +7 -3
- package/src/playback/hls-playback/HlsPlayback.ts +1 -1
- package/src/playback/types.ts +10 -0
- package/src/playback.types.ts +0 -6
- package/src/plugins/multi-camera/MultiCamera.ts +103 -166
- package/src/plugins/source-controller/SourceController.ts +0 -39
- package/src/plugins/subtitles/ClosedCaptions.ts +1 -1
- package/src/plugins/token-refresh/TokenRefreshPlugin.ts +425 -0
- package/src/plugins/token-refresh/index.ts +5 -0
- package/src/utils/mediaSources.ts +10 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/docs/api/player.multicamera.activebyid.md +0 -67
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { $, CorePlugin, Events } from '@clappr/core';
|
|
2
|
+
import HLSJS from 'hls.js';
|
|
3
|
+
import { trace } from '@gcorevideo/utils';
|
|
4
|
+
import { CLAPPR_VERSION } from '../../build.js';
|
|
5
|
+
const T = 'plugins.token_refresh';
|
|
6
|
+
/**
|
|
7
|
+
* Matches the `/{token}/{expires}/` segment in a Gcore protected-content URL.
|
|
8
|
+
* Token is base64url (letters, digits, `-`, `_`); expires is a ≥10-digit Unix timestamp.
|
|
9
|
+
*/
|
|
10
|
+
const TOKEN_SEGMENT_RE = /\/([A-Za-z0-9_-]{6,})\/(1\d{9,})\//;
|
|
11
|
+
function extractTokenState(url) {
|
|
12
|
+
const m = url.match(TOKEN_SEGMENT_RE);
|
|
13
|
+
if (!m)
|
|
14
|
+
return null;
|
|
15
|
+
return { token: m[1], expires: parseInt(m[2], 10) };
|
|
16
|
+
}
|
|
17
|
+
/** Replaces the exact `/{oldToken}/{oldExpires}/` segment in a URL. */
|
|
18
|
+
function rewriteUrl(url, from, to) {
|
|
19
|
+
const oldPart = `/${from.token}/${from.expires}/`;
|
|
20
|
+
const newPart = `/${to.token}/${to.expires}/`;
|
|
21
|
+
return url.includes(oldPart) ? url.replace(oldPart, newPart) : url;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Normalises a URL by removing the `/{token}/{expires}/` segment so two URLs
|
|
25
|
+
* for the same stream with different token pairs compare equal.
|
|
26
|
+
* Returns `null` for unparseable input.
|
|
27
|
+
*/
|
|
28
|
+
function streamKey(url) {
|
|
29
|
+
try {
|
|
30
|
+
const u = new URL(url);
|
|
31
|
+
return u.origin + u.pathname.replace(TOKEN_SEGMENT_RE, '/');
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns a custom hls.js loader class that transparently rewrites the
|
|
39
|
+
* token/expires path segments in every request URL.
|
|
40
|
+
*
|
|
41
|
+
* The returned class extends the default hls.js XhrLoader so all native
|
|
42
|
+
* hls.js behaviour (retry, timeout, range requests …) is preserved.
|
|
43
|
+
*/
|
|
44
|
+
function createTokenRewritingLoader(getOriginal, getCurrent) {
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
const DefaultLoader = HLSJS.DefaultConfig.loader;
|
|
47
|
+
return class TokenRewritingLoader extends DefaultLoader {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
load(context, config, callbacks) {
|
|
50
|
+
const original = getOriginal();
|
|
51
|
+
const current = getCurrent();
|
|
52
|
+
if (original && current && context.url) {
|
|
53
|
+
context.url = rewriteUrl(context.url, original, current);
|
|
54
|
+
}
|
|
55
|
+
super.load(context, config, callbacks);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `PLUGIN` — automatic token refresh for Gcore protected-content streams.
|
|
61
|
+
*
|
|
62
|
+
* Supports all three playback engines:
|
|
63
|
+
*
|
|
64
|
+
* | Engine | Mechanism | Interruption |
|
|
65
|
+
* |--------|-----------|--------------|
|
|
66
|
+
* | **hls.js** | Custom loader rewrites every request URL | None |
|
|
67
|
+
* | **dash.js** | `addRequestInterceptor` rewrites every request URL | None |
|
|
68
|
+
* | **Native `<video>`** (Safari ≤ iOS 14.4) | Source reload + seek restore | Brief |
|
|
69
|
+
*
|
|
70
|
+
* @public
|
|
71
|
+
* @remarks
|
|
72
|
+
* Register the plugin once before creating any player instance:
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { Player, TokenRefreshPlugin } from '@gcorevideo/player'
|
|
75
|
+
* Player.registerPlugin(TokenRefreshPlugin)
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* Then pass `tokenRefresh` in `PlayerConfig`:
|
|
79
|
+
* ```ts
|
|
80
|
+
* const player = new Player({
|
|
81
|
+
* sources: [{ source: initialUrl, mimeType: 'application/x-mpegURL' }],
|
|
82
|
+
* tokenRefresh: {
|
|
83
|
+
* getToken: () => fetch('https://…/token').then(r => r.json()),
|
|
84
|
+
* ipBound: false,
|
|
85
|
+
* refreshLeadSeconds: 5,
|
|
86
|
+
* onTokenRefreshed: (data) => console.log('new token expires', data.expires),
|
|
87
|
+
* },
|
|
88
|
+
* })
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* Safari native — opt-in Service Worker for fully seamless refresh:
|
|
93
|
+
* ```js
|
|
94
|
+
* // Register token-refresh-sw.js (see example/ directory)
|
|
95
|
+
* // and omit tokenRefresh config — the SW handles rewriting.
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export class TokenRefreshPlugin extends CorePlugin {
|
|
99
|
+
/** @internal */
|
|
100
|
+
static get type() {
|
|
101
|
+
return 'core';
|
|
102
|
+
}
|
|
103
|
+
/** @internal */
|
|
104
|
+
get name() {
|
|
105
|
+
return 'token_refresh';
|
|
106
|
+
}
|
|
107
|
+
/** @internal */
|
|
108
|
+
get supportedVersion() {
|
|
109
|
+
return { min: CLAPPR_VERSION };
|
|
110
|
+
}
|
|
111
|
+
/** Token state extracted from the currently-managed source URL */
|
|
112
|
+
originalState = null;
|
|
113
|
+
/** Latest token state (updated after each refresh) */
|
|
114
|
+
currentState = null;
|
|
115
|
+
/** Scheduled refresh timer handle */
|
|
116
|
+
refreshTimer = null;
|
|
117
|
+
/** Playback time (seconds) to restore after a native-video source reload */
|
|
118
|
+
savedPosition = null;
|
|
119
|
+
/** True when using native HTML5 Video playback (no request interception) */
|
|
120
|
+
isNativePlayback = false;
|
|
121
|
+
/** Set in destroy(); short-circuits late timer callbacks and getToken() resolutions */
|
|
122
|
+
destroyed = false;
|
|
123
|
+
/** @internal */
|
|
124
|
+
bindEvents() {
|
|
125
|
+
this.listenTo(this.core, Events.CORE_CONTAINERS_CREATED, this.onContainersCreated);
|
|
126
|
+
}
|
|
127
|
+
/** @internal */
|
|
128
|
+
destroy() {
|
|
129
|
+
this.destroyed = true;
|
|
130
|
+
this.clearTimer();
|
|
131
|
+
super.destroy();
|
|
132
|
+
}
|
|
133
|
+
onContainersCreated() {
|
|
134
|
+
const container = this.core.containers[0];
|
|
135
|
+
if (!container)
|
|
136
|
+
return;
|
|
137
|
+
const playbackName = container.playback.name;
|
|
138
|
+
const src = container.playback.options?.src ?? '';
|
|
139
|
+
trace(`${T} onContainersCreated`, { playbackName, src: src.slice(0, 80) });
|
|
140
|
+
this.isNativePlayback = playbackName !== 'hls' && playbackName !== 'dash';
|
|
141
|
+
const state = extractTokenState(src);
|
|
142
|
+
if (!state) {
|
|
143
|
+
// Active source has no token pattern — drop any refresh state we were
|
|
144
|
+
// holding for a previous stream (e.g. SourceController rotated away).
|
|
145
|
+
if (this.originalState) {
|
|
146
|
+
trace(`${T} active source has no token pattern — clearing refresh state`);
|
|
147
|
+
this.clearTimer();
|
|
148
|
+
this.originalState = null;
|
|
149
|
+
this.currentState = null;
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Adopt the new token state if this is the first source we see, or if
|
|
154
|
+
// the stream has changed (SourceController rotated, consumer called
|
|
155
|
+
// player.load() with a different stream, or the plugin itself reloaded
|
|
156
|
+
// with a refreshed URL in the native path).
|
|
157
|
+
const isNewStream = !this.originalState ||
|
|
158
|
+
state.token !== this.originalState.token ||
|
|
159
|
+
state.expires !== this.originalState.expires;
|
|
160
|
+
if (isNewStream) {
|
|
161
|
+
trace(`${T} adopting source token state`, {
|
|
162
|
+
token: state.token.slice(0, 8) + '…',
|
|
163
|
+
expires: new Date(state.expires * 1000).toISOString(),
|
|
164
|
+
});
|
|
165
|
+
this.originalState = { ...state };
|
|
166
|
+
this.currentState = { ...state };
|
|
167
|
+
this.scheduleRefresh();
|
|
168
|
+
}
|
|
169
|
+
// Inject the appropriate interception mechanism for this playback engine.
|
|
170
|
+
switch (playbackName) {
|
|
171
|
+
case 'hls':
|
|
172
|
+
this.injectHlsLoader(container);
|
|
173
|
+
break;
|
|
174
|
+
case 'dash':
|
|
175
|
+
this.injectDashInterceptor(container);
|
|
176
|
+
break;
|
|
177
|
+
default:
|
|
178
|
+
// Native HTML5 Video — no request hooks available.
|
|
179
|
+
// Seek restore after a token-triggered reload.
|
|
180
|
+
this.listenToOnce(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChangedForNative);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
injectHlsLoader(container) {
|
|
185
|
+
const getOriginal = () => this.originalState;
|
|
186
|
+
const getCurrent = () => this.currentState;
|
|
187
|
+
const TokenLoader = createTokenRewritingLoader(getOriginal, getCurrent);
|
|
188
|
+
$.extend(true, container.playback.options, {
|
|
189
|
+
playback: {
|
|
190
|
+
hlsjsConfig: {
|
|
191
|
+
loader: TokenLoader,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
trace(`${T} HLS custom loader injected`);
|
|
196
|
+
}
|
|
197
|
+
injectDashInterceptor(container) {
|
|
198
|
+
$.extend(true, container.playback.options, {
|
|
199
|
+
dash: {
|
|
200
|
+
requestInterceptor: (request) => {
|
|
201
|
+
if (this.originalState && this.currentState) {
|
|
202
|
+
request.url = rewriteUrl(request.url, this.originalState, this.currentState);
|
|
203
|
+
}
|
|
204
|
+
return Promise.resolve(request);
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
trace(`${T} DASH request interceptor injected`);
|
|
209
|
+
}
|
|
210
|
+
async reloadNativeSource(data) {
|
|
211
|
+
const container = this.core.activeContainer;
|
|
212
|
+
const playback = container?.playback;
|
|
213
|
+
if (!playback)
|
|
214
|
+
return;
|
|
215
|
+
// SourceController (or any other actor) may have switched the active
|
|
216
|
+
// playback to hls/dash while getToken() was in flight. Those engines
|
|
217
|
+
// have their own request-interception path; a native reload would
|
|
218
|
+
// undo the switch.
|
|
219
|
+
if (playback.name === 'hls' || playback.name === 'dash') {
|
|
220
|
+
trace(`${T} skipping native reload — active playback is ${playback.name}`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!this.isNativePlayback) {
|
|
224
|
+
trace(`${T} skipping native reload — no longer in native playback mode`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Verify the URL we're about to load belongs to the same stream that's
|
|
228
|
+
// currently active. If SourceController rotated to a different stream
|
|
229
|
+
// during the getToken() await, the refreshed URL would silently swap
|
|
230
|
+
// us back, undoing SourceController's decision.
|
|
231
|
+
const currentSrc = playback.options?.src ?? '';
|
|
232
|
+
const newUrl = this.opts.ipBound ? data.url_ip : data.url;
|
|
233
|
+
const activeKey = streamKey(currentSrc);
|
|
234
|
+
const nextKey = streamKey(newUrl);
|
|
235
|
+
if (!activeKey || !nextKey || activeKey !== nextKey) {
|
|
236
|
+
trace(`${T} skipping native reload — active source differs from refresh URL`, {
|
|
237
|
+
activeKey,
|
|
238
|
+
nextKey,
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Capture current playback position before tearing down the container.
|
|
243
|
+
const mediaEl = playback.el;
|
|
244
|
+
const currentTime = mediaEl?.currentTime ?? 0;
|
|
245
|
+
this.savedPosition = currentTime > 0 ? currentTime : null;
|
|
246
|
+
trace(`${T} native reload`, { newUrl: newUrl.slice(0, 80), savedPosition: this.savedPosition });
|
|
247
|
+
// core.load() destroys and recreates all containers.
|
|
248
|
+
this.core.load([{ source: newUrl, mimeType: this.core.options.mimeType ?? 'application/x-mpegURL' }]);
|
|
249
|
+
}
|
|
250
|
+
onActiveContainerChangedForNative() {
|
|
251
|
+
if (this.savedPosition === null)
|
|
252
|
+
return;
|
|
253
|
+
const pos = this.savedPosition;
|
|
254
|
+
this.savedPosition = null;
|
|
255
|
+
// Wait for the new container to be fully ready before seeking.
|
|
256
|
+
const container = this.core.activeContainer;
|
|
257
|
+
if (!container)
|
|
258
|
+
return;
|
|
259
|
+
this.listenToOnce(container, Events.CONTAINER_READY, () => {
|
|
260
|
+
trace(`${T} native: restoring position`, { pos });
|
|
261
|
+
container.seek(pos);
|
|
262
|
+
container.play();
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
scheduleRefresh() {
|
|
266
|
+
this.clearTimer();
|
|
267
|
+
if (this.destroyed || !this.currentState)
|
|
268
|
+
return;
|
|
269
|
+
const leadMs = (this.opts.refreshLeadSeconds ?? 5) * 1000;
|
|
270
|
+
const msUntilRefresh = this.currentState.expires * 1000 - Date.now() - leadMs;
|
|
271
|
+
trace(`${T} next refresh in`, {
|
|
272
|
+
seconds: Math.round(msUntilRefresh / 1000),
|
|
273
|
+
expires: new Date(this.currentState.expires * 1000).toISOString(),
|
|
274
|
+
});
|
|
275
|
+
this.refreshTimer = setTimeout(() => this.performRefresh(), Math.max(msUntilRefresh, 1000));
|
|
276
|
+
}
|
|
277
|
+
async performRefresh() {
|
|
278
|
+
trace(`${T} fetching new token`);
|
|
279
|
+
try {
|
|
280
|
+
const data = await this.opts.getToken();
|
|
281
|
+
// Plugin may have been destroyed while getToken() was in flight; drop the result.
|
|
282
|
+
if (this.destroyed)
|
|
283
|
+
return;
|
|
284
|
+
const newToken = this.opts.ipBound ? data.token_ip : data.token;
|
|
285
|
+
const newState = { token: newToken, expires: data.expires };
|
|
286
|
+
if (this.isNativePlayback) {
|
|
287
|
+
// Must reload source because the <video> element has no request hook.
|
|
288
|
+
await this.reloadNativeSource(data);
|
|
289
|
+
}
|
|
290
|
+
// originalState is never changed after init — it holds the token that was
|
|
291
|
+
// baked into every URL in the initial manifest. hls.js/dash.js always
|
|
292
|
+
// produces request URLs based on that manifest, so every segment URL
|
|
293
|
+
// still contains the original token regardless of how many refreshes
|
|
294
|
+
// have already happened. The loader replaces original→current on each
|
|
295
|
+
// request, so updating only currentState is sufficient.
|
|
296
|
+
this.currentState = newState;
|
|
297
|
+
this.opts.onTokenRefreshed?.(data);
|
|
298
|
+
trace(`${T} token refreshed`, {
|
|
299
|
+
token: newToken.slice(0, 8) + '…',
|
|
300
|
+
expires: new Date(data.expires * 1000).toISOString(),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
trace(`${T} token refresh failed`, { err });
|
|
305
|
+
}
|
|
306
|
+
// Always reschedule, even after an error (will retry near next expiry).
|
|
307
|
+
this.scheduleRefresh();
|
|
308
|
+
}
|
|
309
|
+
get opts() {
|
|
310
|
+
return this.options.tokenRefresh;
|
|
311
|
+
}
|
|
312
|
+
clearTimer() {
|
|
313
|
+
if (this.refreshTimer !== null) {
|
|
314
|
+
clearTimeout(this.refreshTimer);
|
|
315
|
+
this.refreshTimer = null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/token-refresh/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TokenRefreshPlugin, } from './TokenRefreshPlugin.js';
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { PlayerMediaSource, PlayerMediaSourceDesc, TransportPreference } from '../types';
|
|
2
|
+
export declare const MIME_TYPES_HLS: string[];
|
|
3
|
+
export declare const MIME_TYPE_HLS: string;
|
|
4
|
+
export declare const MIME_TYPE_DASH = "application/dash+xml";
|
|
2
5
|
export declare function buildMediaSourcesList(sources: PlayerMediaSourceDesc[], priorityTransport?: TransportPreference): PlayerMediaSourceDesc[];
|
|
3
6
|
export declare function unwrapSource(s: PlayerMediaSource): string;
|
|
4
7
|
export declare function wrapSource(s: PlayerMediaSource): PlayerMediaSourceDesc;
|
|
8
|
+
export declare function guessMimeType(s: string): string | undefined;
|
|
5
9
|
export declare function isDashSource(source: string, mimeType?: string): boolean;
|
|
6
10
|
export declare function isHlsSource(source: string, mimeType?: string): boolean;
|
|
7
11
|
//# sourceMappingURL=mediaSources.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mediaSources.d.ts","sourceRoot":"","sources":["../../src/utils/mediaSources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"mediaSources.d.ts","sourceRoot":"","sources":["../../src/utils/mediaSources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,UAAU,CAAA;AAGjB,eAAO,MAAM,cAAc,UAA6D,CAAA;AACxF,eAAO,MAAM,aAAa,QAAoB,CAAA;AAC9C,eAAO,MAAM,cAAc,yBAAyB,CAAA;AAGpD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,qBAAqB,EAAE,EAChC,iBAAiB,GAAE,mBAA4B,GAC9C,qBAAqB,EAAE,CAqCzB;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAEzD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,iBAAiB,GAAG,qBAAqB,CAEtE;AAGD,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO3D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,WAK7D;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,WAO5D"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Loader } from '@clappr/core';
|
|
2
2
|
import { trace } from '@gcorevideo/utils';
|
|
3
|
+
export const MIME_TYPES_HLS = ['application/x-mpegurl', 'application/vnd.apple.mpegurl'];
|
|
4
|
+
export const MIME_TYPE_HLS = MIME_TYPES_HLS[0];
|
|
5
|
+
export const MIME_TYPE_DASH = 'application/dash+xml';
|
|
3
6
|
// TODO rewrite using the Playback classes and canPlay static methods
|
|
4
7
|
export function buildMediaSourcesList(sources, priorityTransport = 'dash') {
|
|
5
8
|
const playbacks = Loader.registeredPlaybacks;
|
|
@@ -40,24 +43,23 @@ export function unwrapSource(s) {
|
|
|
40
43
|
export function wrapSource(s) {
|
|
41
44
|
return typeof s === 'string' ? { source: s, mimeType: guessMimeType(s) } : s;
|
|
42
45
|
}
|
|
43
|
-
function guessMimeType(s) {
|
|
46
|
+
export function guessMimeType(s) {
|
|
44
47
|
if (s.endsWith('.mpd')) {
|
|
45
|
-
return
|
|
48
|
+
return MIME_TYPE_DASH;
|
|
46
49
|
}
|
|
47
50
|
if (s.endsWith('.m3u8')) {
|
|
48
|
-
|
|
49
|
-
return 'application/x-mpegurl';
|
|
51
|
+
return MIME_TYPE_HLS;
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
export function isDashSource(source, mimeType) {
|
|
53
55
|
if (mimeType) {
|
|
54
|
-
return mimeType ===
|
|
56
|
+
return mimeType === MIME_TYPE_DASH; // TODO consider video/mp4
|
|
55
57
|
}
|
|
56
58
|
return source.endsWith('.mpd');
|
|
57
59
|
}
|
|
58
60
|
export function isHlsSource(source, mimeType) {
|
|
59
61
|
if (mimeType) {
|
|
60
|
-
return
|
|
62
|
+
return MIME_TYPES_HLS.includes(mimeType.toLowerCase());
|
|
61
63
|
}
|
|
62
64
|
return source.endsWith('.m3u8');
|
|
63
65
|
}
|
package/package.json
CHANGED
package/src/Player.ts
CHANGED
|
@@ -278,6 +278,18 @@ export class Player {
|
|
|
278
278
|
this.player?.load(ms, ms[0].mimeType ?? '')
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Returns a registered core plugin instance by name, or `null` if not found.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* const tokenRefresh = player.getPlugin('token_refresh') as TokenRefreshPlugin | null
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
getPlugin(name: string) {
|
|
290
|
+
return this.player?.core.getPlugin(name) ?? null
|
|
291
|
+
}
|
|
292
|
+
|
|
281
293
|
/**
|
|
282
294
|
* Mutes the sound of the video.
|
|
283
295
|
*/
|
package/src/index.plugins.ts
CHANGED
|
@@ -30,11 +30,10 @@ import {
|
|
|
30
30
|
QualityLevel,
|
|
31
31
|
TimePosition,
|
|
32
32
|
TimeValue,
|
|
33
|
-
VTTCueInfo,
|
|
34
33
|
} from '../../playback.types.js'
|
|
35
34
|
import { isDashSource } from '../../utils/mediaSources.js'
|
|
36
35
|
import { BasePlayback } from '../BasePlayback.js'
|
|
37
|
-
import { PlaybackEvents } from '../types.js'
|
|
36
|
+
import { PlaybackEvents, VTTCueInfo } from '../types.js'
|
|
38
37
|
import { AudioTrack } from '@clappr/core/types/base/playback/playback.js'
|
|
39
38
|
|
|
40
39
|
const AUTO = -1
|
|
@@ -244,6 +243,7 @@ export default class DashPlayback extends BasePlayback {
|
|
|
244
243
|
this._dash.initialize()
|
|
245
244
|
|
|
246
245
|
if (this.options.dash) {
|
|
246
|
+
const { requestInterceptor, ...dashSettings } = this.options.dash as Record<string, unknown>
|
|
247
247
|
const settings = $.extend(
|
|
248
248
|
true,
|
|
249
249
|
{
|
|
@@ -258,9 +258,13 @@ export default class DashPlayback extends BasePlayback {
|
|
|
258
258
|
},
|
|
259
259
|
},
|
|
260
260
|
},
|
|
261
|
-
|
|
261
|
+
dashSettings,
|
|
262
262
|
)
|
|
263
263
|
this._dash.updateSettings(settings)
|
|
264
|
+
|
|
265
|
+
if (typeof requestInterceptor === 'function') {
|
|
266
|
+
this._dash.addRequestInterceptor(requestInterceptor as Parameters<typeof this._dash.addRequestInterceptor>[0])
|
|
267
|
+
}
|
|
264
268
|
}
|
|
265
269
|
|
|
266
270
|
this._dash.attachView(this.el as HTMLMediaElement)
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
PlayerComponentType,
|
|
27
27
|
QualityLevel,
|
|
28
28
|
TimePosition,
|
|
29
|
-
VTTCueInfo,
|
|
30
29
|
} from '../../playback.types.js'
|
|
31
30
|
import { PlaybackType } from '../../types.js'
|
|
32
31
|
import { isHlsSource } from '../../utils/mediaSources.js'
|
|
@@ -35,6 +34,7 @@ import { BasePlayback } from '../BasePlayback.js'
|
|
|
35
34
|
|
|
36
35
|
import { CLAPPR_VERSION } from '../../build.js'
|
|
37
36
|
import { AudioTrack } from '@clappr/core/types/base/playback/playback.js'
|
|
37
|
+
import { VTTCueInfo } from '../types.js'
|
|
38
38
|
|
|
39
39
|
const { now } = Utils
|
|
40
40
|
|
package/src/playback/types.ts
CHANGED