@gcorevideo/player 2.29.0 → 2.30.1
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/dist/core.js +81 -22
- package/dist/index.css +370 -370
- package/dist/index.embed.js +80 -23
- package/dist/index.js +459 -87
- package/docs/api/player.md +37 -0
- package/docs/api/player.player.getplugin.md +59 -0
- package/docs/api/player.player.md +14 -0
- package/docs/api/player.tokenrefreshoptions.gettoken.md +13 -0
- package/docs/api/player.tokenrefreshoptions.ipbound.md +13 -0
- package/docs/api/player.tokenrefreshoptions.md +115 -0
- package/docs/api/player.tokenrefreshoptions.ontokenrefreshed.md +13 -0
- package/docs/api/player.tokenrefreshoptions.refreshleadseconds.md +13 -0
- package/docs/api/player.tokenrefreshplugin.md +50 -0
- package/docs/api/player.tokenresponse.client_ip.md +13 -0
- package/docs/api/player.tokenresponse.expires.md +13 -0
- package/docs/api/player.tokenresponse.md +153 -0
- package/docs/api/player.tokenresponse.token.md +13 -0
- package/docs/api/player.tokenresponse.token_ip.md +13 -0
- package/docs/api/player.tokenresponse.url.md +13 -0
- package/docs/api/player.tokenresponse.url_ip.md +13 -0
- 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.map +1 -1
- package/lib/playback/dash-playback/DashPlayback.js +5 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts +1 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/hls-playback/HlsPlayback.js +23 -20
- package/lib/playback/hls-playback/RangesList.d.ts +7 -0
- package/lib/playback/hls-playback/RangesList.d.ts.map +1 -0
- package/lib/playback/hls-playback/RangesList.js +41 -0
- package/lib/plugins/subtitles/ClosedCaptions.d.ts.map +1 -1
- package/lib/plugins/subtitles/ClosedCaptions.js +0 -2
- 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/package.json +1 -1
- package/src/Player.ts +12 -0
- package/src/index.plugins.ts +1 -0
- package/src/playback/dash-playback/DashPlayback.ts +6 -1
- package/src/playback/hls-playback/HlsPlayback.ts +40 -37
- package/src/playback/hls-playback/RangesList.ts +45 -0
- package/src/playback/hls-playback/__tests__/RangesList.test.ts +60 -0
- package/src/plugins/subtitles/ClosedCaptions.ts +0 -5
- package/src/plugins/token-refresh/TokenRefreshPlugin.ts +425 -0
- package/src/plugins/token-refresh/index.ts +5 -0
- package/tsconfig.tsbuildinfo +1 -1
package/README.md
CHANGED
|
@@ -81,6 +81,114 @@ See the complete example app on Vercel: <https://github.com/dmitritz/gcore-video
|
|
|
81
81
|
|
|
82
82
|
[Example codepen](https://codepen.io/dmitritz/pen/OPLdEab)
|
|
83
83
|
|
|
84
|
+
## Protected-content streams — automatic token refresh
|
|
85
|
+
|
|
86
|
+
Gcore protected-content streams embed a security token and expiry timestamp
|
|
87
|
+
directly in the URL path:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
https://host/videos/{video-id}/{token}/{expires}/master.m3u8
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Once a token expires the CDN returns HTTP 401 for every segment request.
|
|
94
|
+
`TokenRefreshPlugin` handles renewal transparently: it fires a background timer
|
|
95
|
+
before the token expires, calls your `getToken()` function, and rewrites the
|
|
96
|
+
`/{token}/{expires}/` segment in every outgoing hls.js or dash.js request URL —
|
|
97
|
+
playback continues without buffering or interruption.
|
|
98
|
+
|
|
99
|
+
### Supported playback engines
|
|
100
|
+
|
|
101
|
+
| Engine | Mechanism | Interruption |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| **hls.js** | Custom loader rewrites every request URL before XHR `open()` | None |
|
|
104
|
+
| **dash.js** | `addRequestInterceptor` rewrites every request URL | None |
|
|
105
|
+
| **Native `<video>`** (older Safari) | Source reload + seek restore | Brief |
|
|
106
|
+
|
|
107
|
+
For fully seamless refresh on older Safari, register `example/token-refresh-sw.js`
|
|
108
|
+
as a Service Worker — it intercepts all CDN fetch requests and rewrites the token
|
|
109
|
+
even for native media elements.
|
|
110
|
+
|
|
111
|
+
### Usage
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { Player, TokenRefreshPlugin } from '@gcorevideo/player'
|
|
115
|
+
|
|
116
|
+
// Register once before creating any player instance.
|
|
117
|
+
Player.registerPlugin(TokenRefreshPlugin)
|
|
118
|
+
|
|
119
|
+
const player = new Player({
|
|
120
|
+
sources: [{
|
|
121
|
+
// The token API returns a ready-to-use URL with the token embedded in the path.
|
|
122
|
+
// TokenRefreshPlugin reads the initial {token}/{expires} from this URL at startup.
|
|
123
|
+
source: 'https://host/videos/{id}/{token}/{expires}/master.m3u8',
|
|
124
|
+
mimeType: 'application/x-mpegURL',
|
|
125
|
+
}],
|
|
126
|
+
|
|
127
|
+
tokenRefresh: {
|
|
128
|
+
/**
|
|
129
|
+
* Called automatically ~refreshLeadSeconds before the current token expires.
|
|
130
|
+
* Must return a Promise resolving to a TokenResponse object.
|
|
131
|
+
*/
|
|
132
|
+
getToken: () => fetch('https://your-token-api/token').then(r => r.json()),
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Set to true to use IP-bound tokens (token_ip / url_ip).
|
|
136
|
+
* All CDN requests must then originate from the same IP as the first response.
|
|
137
|
+
* Default: false.
|
|
138
|
+
*/
|
|
139
|
+
ipBound: false,
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* How many seconds before expiry to pre-fetch the new token.
|
|
143
|
+
* Rule of thumb: refreshLeadSeconds < tokenLifetime / 2.
|
|
144
|
+
* Default: 5.
|
|
145
|
+
*/
|
|
146
|
+
refreshLeadSeconds: 5,
|
|
147
|
+
|
|
148
|
+
/** Optional callback fired after each successful token refresh. */
|
|
149
|
+
onTokenRefreshed(data) {
|
|
150
|
+
console.log('token refreshed, new expiry:', new Date(data.expires * 1000))
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
player.attachTo(document.getElementById('player'))
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### TokenResponse shape
|
|
159
|
+
|
|
160
|
+
Your `getToken()` function must return an object with this structure:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
interface TokenResponse {
|
|
164
|
+
token: string // plain (any-IP) token
|
|
165
|
+
token_ip: string // IP-bound token
|
|
166
|
+
client_ip: string // client IP the token server observed
|
|
167
|
+
expires: number // Unix timestamp (seconds) when both tokens expire
|
|
168
|
+
url: string // full HLS master URL with plain token in path
|
|
169
|
+
url_ip: string // full HLS master URL with IP-bound token in path
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Pausing and resuming refresh
|
|
174
|
+
|
|
175
|
+
Use the plugin instance to suspend and resume the refresh cycle at runtime:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const plugin = player.getPlugin('token_refresh')
|
|
179
|
+
|
|
180
|
+
plugin.pause() // stop the timer; existing token stays active until CDN rejects it
|
|
181
|
+
plugin.resume() // restart the timer; fetches immediately if token already expired
|
|
182
|
+
console.log(plugin.isPaused) // → true | false
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Working demo
|
|
186
|
+
|
|
187
|
+
See [`example/protected-content.html`](../../example/protected-content.html) and
|
|
188
|
+
[`example/protected-content.js`](../../example/protected-content.js) for a fully
|
|
189
|
+
annotated end-to-end integration, including UI feedback, IP-bound token switching,
|
|
190
|
+
a live countdown, and Service Worker integration notes.
|
|
191
|
+
|
|
84
192
|
## Documentation
|
|
85
193
|
|
|
86
194
|
- [API reference](./docs/api/index.md)
|
package/dist/core.js
CHANGED
|
@@ -12963,6 +12963,7 @@ class DashPlayback extends BasePlayback {
|
|
|
12963
12963
|
this._dash = dash;
|
|
12964
12964
|
this._dash.initialize();
|
|
12965
12965
|
if (this.options.dash) {
|
|
12966
|
+
const { requestInterceptor, ...dashSettings } = this.options.dash;
|
|
12966
12967
|
const settings = $.extend(true, {
|
|
12967
12968
|
streaming: {
|
|
12968
12969
|
text: {
|
|
@@ -12974,8 +12975,11 @@ class DashPlayback extends BasePlayback {
|
|
|
12974
12975
|
// dispatchForManualRendering: true, // TODO only when useNativeSubtitles is not true?
|
|
12975
12976
|
},
|
|
12976
12977
|
},
|
|
12977
|
-
},
|
|
12978
|
+
}, dashSettings);
|
|
12978
12979
|
this._dash.updateSettings(settings);
|
|
12980
|
+
if (typeof requestInterceptor === 'function') {
|
|
12981
|
+
this._dash.addRequestInterceptor(requestInterceptor);
|
|
12982
|
+
}
|
|
12979
12983
|
}
|
|
12980
12984
|
this._dash.attachView(this.el);
|
|
12981
12985
|
this._dash.setAutoPlay(false);
|
|
@@ -49996,6 +50000,48 @@ Hls.defaultConfig = void 0;
|
|
|
49996
50000
|
// export const CLAPPR_VERSION: string = process.env.CLAPPR_VERSION || '0.11.3';
|
|
49997
50001
|
const CLAPPR_VERSION = '0.13.0';
|
|
49998
50002
|
|
|
50003
|
+
class RangesList {
|
|
50004
|
+
// TODO write an efficient implementation
|
|
50005
|
+
items = [];
|
|
50006
|
+
insert(start, end, value) {
|
|
50007
|
+
const index = this.findIndex((start + end) / 2);
|
|
50008
|
+
this.items.splice(index, 0, [start, end, value]);
|
|
50009
|
+
}
|
|
50010
|
+
find(position) {
|
|
50011
|
+
const index = this.findIndex(position);
|
|
50012
|
+
const item = this.items[index];
|
|
50013
|
+
if (!item || item[0] > position || item[1] < position) {
|
|
50014
|
+
return null;
|
|
50015
|
+
}
|
|
50016
|
+
return item[2];
|
|
50017
|
+
}
|
|
50018
|
+
findIndex(position) {
|
|
50019
|
+
let low = 0;
|
|
50020
|
+
let high = this.items.length;
|
|
50021
|
+
let index = 0;
|
|
50022
|
+
while (low < high) {
|
|
50023
|
+
index = low + Math.floor((high - low) / 2);
|
|
50024
|
+
const item = this.items[index];
|
|
50025
|
+
if (item[0] > position) {
|
|
50026
|
+
if (index === low) {
|
|
50027
|
+
return index;
|
|
50028
|
+
}
|
|
50029
|
+
high = index;
|
|
50030
|
+
continue;
|
|
50031
|
+
}
|
|
50032
|
+
if (item[1] <= position) {
|
|
50033
|
+
if (index === high - 1) {
|
|
50034
|
+
return index + 1;
|
|
50035
|
+
}
|
|
50036
|
+
low = index + 1;
|
|
50037
|
+
continue;
|
|
50038
|
+
}
|
|
50039
|
+
break;
|
|
50040
|
+
}
|
|
50041
|
+
return index;
|
|
50042
|
+
}
|
|
50043
|
+
}
|
|
50044
|
+
|
|
49999
50045
|
// Copyright 2014 Globo.com Player authors. All rights reserved.
|
|
50000
50046
|
// Use of this source code is governed by a BSD-style
|
|
50001
50047
|
// license that can be found on https://github.com/clappr/hlsjs-playback/blob/main/LICENSE
|
|
@@ -50005,7 +50051,6 @@ const DEFAULT_RECOVER_ATTEMPTS = 16;
|
|
|
50005
50051
|
Events$1.register('PLAYBACK_FRAGMENT_PARSING_METADATA');
|
|
50006
50052
|
const T$2 = 'playback.hls';
|
|
50007
50053
|
class HlsPlayback extends BasePlayback {
|
|
50008
|
-
_ccTracksUpdated = false;
|
|
50009
50054
|
_currentFragment = null;
|
|
50010
50055
|
_currentLevel = null;
|
|
50011
50056
|
_durationExcludesAfterLiveSyncPoint = false;
|
|
@@ -50030,7 +50075,8 @@ class HlsPlayback extends BasePlayback {
|
|
|
50030
50075
|
_timeUpdateTimer = null;
|
|
50031
50076
|
oncueenter = null;
|
|
50032
50077
|
oncueexit = null;
|
|
50033
|
-
cues =
|
|
50078
|
+
cues = null;
|
|
50079
|
+
cuesByTrack = {};
|
|
50034
50080
|
currentCueId = null;
|
|
50035
50081
|
/**
|
|
50036
50082
|
* @internal
|
|
@@ -50227,7 +50273,6 @@ class HlsPlayback extends BasePlayback {
|
|
|
50227
50273
|
}
|
|
50228
50274
|
this._manifestParsed = false;
|
|
50229
50275
|
// this._ccIsSetup = false
|
|
50230
|
-
this._ccTracksUpdated = false;
|
|
50231
50276
|
this._setInitialState();
|
|
50232
50277
|
this._hls.destroy();
|
|
50233
50278
|
this._hls = null;
|
|
@@ -50281,12 +50326,20 @@ class HlsPlayback extends BasePlayback {
|
|
|
50281
50326
|
this._hls.on(Events.AUDIO_TRACK_SWITCHED, (evt, data) => this._onAudioTrackSwitched(evt, data));
|
|
50282
50327
|
this._hls.on(Events.CUES_PARSED, (evt, data) => {
|
|
50283
50328
|
data.cues?.forEach((cue) => {
|
|
50284
|
-
this.cues
|
|
50329
|
+
if (!this.cues) {
|
|
50330
|
+
const trackId = this._hls.subtitleTrack;
|
|
50331
|
+
if (!this.cuesByTrack[trackId]) {
|
|
50332
|
+
this.cuesByTrack[trackId] = new RangesList();
|
|
50333
|
+
}
|
|
50334
|
+
this.cues = this.cuesByTrack[trackId];
|
|
50335
|
+
}
|
|
50336
|
+
const cueInfo = {
|
|
50285
50337
|
id: cue.id,
|
|
50286
50338
|
start: cue.startTime,
|
|
50287
50339
|
end: cue.endTime,
|
|
50288
50340
|
text: cue.text,
|
|
50289
|
-
}
|
|
50341
|
+
};
|
|
50342
|
+
this.cues.insert(cue.startTime, cue.endTime, cueInfo);
|
|
50290
50343
|
});
|
|
50291
50344
|
});
|
|
50292
50345
|
this.bindCustomListeners();
|
|
@@ -50513,7 +50566,7 @@ class HlsPlayback extends BasePlayback {
|
|
|
50513
50566
|
}
|
|
50514
50567
|
}
|
|
50515
50568
|
reload() {
|
|
50516
|
-
this.cues =
|
|
50569
|
+
this.cues = null;
|
|
50517
50570
|
this.currentCueId = null;
|
|
50518
50571
|
this._hls?.startLoad(-1);
|
|
50519
50572
|
}
|
|
@@ -50578,9 +50631,7 @@ class HlsPlayback extends BasePlayback {
|
|
|
50578
50631
|
}
|
|
50579
50632
|
triggerCues() {
|
|
50580
50633
|
const currentTime = this.getCurrentTime();
|
|
50581
|
-
|
|
50582
|
-
// TODO build a search tree
|
|
50583
|
-
const cue = this.cues.find((cue) => currentTime >= cue.start && currentTime <= cue.end);
|
|
50634
|
+
const cue = this.cues?.find(currentTime);
|
|
50584
50635
|
if (cue) {
|
|
50585
50636
|
this.currentCueId = cue.id;
|
|
50586
50637
|
this.oncueenter?.(cue);
|
|
@@ -50623,20 +50674,14 @@ class HlsPlayback extends BasePlayback {
|
|
|
50623
50674
|
destroy() {
|
|
50624
50675
|
this._stopTimeUpdateTimer();
|
|
50625
50676
|
this._destroyHLSInstance();
|
|
50677
|
+
this.cues = null;
|
|
50678
|
+
this.cuesByTrack = {};
|
|
50626
50679
|
return super.destroy();
|
|
50627
50680
|
}
|
|
50628
50681
|
_updatePlaybackType(evt, data) {
|
|
50629
50682
|
const prevPlaybackType = this._playbackType;
|
|
50630
50683
|
this._playbackType = (data.details.live ? Playback.LIVE : Playback.VOD);
|
|
50631
50684
|
this._onLevelUpdated(evt, data);
|
|
50632
|
-
// Live stream subtitle tracks detection hack (may not immediately available)
|
|
50633
|
-
// if (
|
|
50634
|
-
// this._ccTracksUpdated &&
|
|
50635
|
-
// this._playbackType === Playback.LIVE &&
|
|
50636
|
-
// this.hasClosedCaptionsTracks
|
|
50637
|
-
// ) {
|
|
50638
|
-
// this._onSubtitleLoaded()
|
|
50639
|
-
// }
|
|
50640
50685
|
if (prevPlaybackType !== this._playbackType) {
|
|
50641
50686
|
this._updateSettings();
|
|
50642
50687
|
}
|
|
@@ -50857,7 +50902,10 @@ class HlsPlayback extends BasePlayback {
|
|
|
50857
50902
|
return;
|
|
50858
50903
|
}
|
|
50859
50904
|
this._hls.subtitleTrack = id;
|
|
50860
|
-
this.
|
|
50905
|
+
if (!this.cuesByTrack[id]) {
|
|
50906
|
+
this.cuesByTrack[id] = new RangesList();
|
|
50907
|
+
}
|
|
50908
|
+
this.cues = this.cuesByTrack[id];
|
|
50861
50909
|
}
|
|
50862
50910
|
/**
|
|
50863
50911
|
* @override
|
|
@@ -50866,7 +50914,7 @@ class HlsPlayback extends BasePlayback {
|
|
|
50866
50914
|
return this.getTextTracks();
|
|
50867
50915
|
}
|
|
50868
50916
|
getTextTracks() {
|
|
50869
|
-
return this._hls?.subtitleTracks.map((t) => ({
|
|
50917
|
+
return (this._hls?.subtitleTracks.map((t) => ({
|
|
50870
50918
|
id: t.id,
|
|
50871
50919
|
name: t.name,
|
|
50872
50920
|
track: {
|
|
@@ -50874,7 +50922,7 @@ class HlsPlayback extends BasePlayback {
|
|
|
50874
50922
|
label: t.name,
|
|
50875
50923
|
language: t.lang,
|
|
50876
50924
|
},
|
|
50877
|
-
})) || [];
|
|
50925
|
+
})) || []);
|
|
50878
50926
|
}
|
|
50879
50927
|
}
|
|
50880
50928
|
HlsPlayback.canPlay = function (resource, mimeType) {
|
|
@@ -51206,6 +51254,17 @@ class Player {
|
|
|
51206
51254
|
}
|
|
51207
51255
|
this.player?.load(ms, ms[0].mimeType ?? '');
|
|
51208
51256
|
}
|
|
51257
|
+
/**
|
|
51258
|
+
* Returns a registered core plugin instance by name, or `null` if not found.
|
|
51259
|
+
*
|
|
51260
|
+
* @example
|
|
51261
|
+
* ```ts
|
|
51262
|
+
* const tokenRefresh = player.getPlugin('token_refresh') as TokenRefreshPlugin | null
|
|
51263
|
+
* ```
|
|
51264
|
+
*/
|
|
51265
|
+
getPlugin(name) {
|
|
51266
|
+
return this.player?.core.getPlugin(name) ?? null;
|
|
51267
|
+
}
|
|
51209
51268
|
/**
|
|
51210
51269
|
* Mutes the sound of the video.
|
|
51211
51270
|
*/
|
|
@@ -51477,7 +51536,7 @@ class Player {
|
|
|
51477
51536
|
}
|
|
51478
51537
|
}
|
|
51479
51538
|
|
|
51480
|
-
var version$1 = "2.
|
|
51539
|
+
var version$1 = "2.30.1";
|
|
51481
51540
|
|
|
51482
51541
|
var packages = {
|
|
51483
51542
|
"node_modules/@clappr/core": {
|