rx-player 3.28.0-dev.2022063000 → 3.28.1-dev.2022083000
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/.github/workflows/checks.yml +20 -18
- package/CHANGELOG.md +14 -2
- package/VERSION +1 -1
- package/dist/_esm5.processed/compat/eme/load_session.d.ts +5 -6
- package/dist/_esm5.processed/compat/eme/load_session.js +5 -6
- package/dist/_esm5.processed/compat/event_listeners.js +5 -0
- package/dist/_esm5.processed/core/api/media_element_track_choice_manager.js +1 -1
- package/dist/_esm5.processed/core/api/public_api.js +2 -2
- package/dist/_esm5.processed/core/decrypt/create_session.js +33 -4
- package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.d.ts +14 -0
- package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.js +25 -0
- package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.d.ts +1 -1
- package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +1 -1
- package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +110 -38
- package/dist/_esm5.processed/core/stream/representation/check_for_discontinuity.js +21 -9
- package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +21 -29
- package/dist/_esm5.processed/errors/encrypted_media_error.d.ts +1 -2
- package/dist/_esm5.processed/errors/encrypted_media_error.js +0 -1
- package/dist/_esm5.processed/errors/error_codes.d.ts +6 -1
- package/dist/_esm5.processed/errors/media_error.d.ts +1 -2
- package/dist/_esm5.processed/errors/media_error.js +0 -1
- package/dist/_esm5.processed/errors/network_error.d.ts +1 -2
- package/dist/_esm5.processed/errors/network_error.js +0 -1
- package/dist/_esm5.processed/errors/other_error.d.ts +1 -2
- package/dist/_esm5.processed/errors/other_error.js +0 -1
- package/dist/_esm5.processed/manifest/manifest.d.ts +1 -1
- package/dist/_esm5.processed/manifest/manifest.js +1 -1
- package/dist/_esm5.processed/manifest/representation_index/static.d.ts +21 -6
- package/dist/_esm5.processed/manifest/representation_index/static.js +26 -8
- package/dist/_esm5.processed/manifest/representation_index/types.d.ts +55 -44
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.d.ts +21 -8
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +25 -10
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +26 -12
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +26 -13
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.d.ts +23 -7
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +65 -22
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.d.ts +20 -3
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +57 -7
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/{is_period_fulfilled.d.ts → utils.d.ts} +3 -6
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/{is_period_fulfilled.js → utils.js} +4 -8
- package/dist/_esm5.processed/parsers/manifest/dash/common/parse_periods.js +1 -1
- package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +5 -8
- package/dist/_esm5.processed/parsers/manifest/local/representation_index.d.ts +21 -8
- package/dist/_esm5.processed/parsers/manifest/local/representation_index.js +49 -14
- package/dist/_esm5.processed/parsers/manifest/local/types.d.ts +16 -0
- package/dist/_esm5.processed/parsers/manifest/metaplaylist/representation_index.d.ts +20 -6
- package/dist/_esm5.processed/parsers/manifest/metaplaylist/representation_index.js +28 -10
- package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +4 -4
- package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.d.ts +21 -12
- package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.js +39 -14
- package/dist/_esm5.processed/parsers/manifest/utils/get_first_time_from_adaptation.js +1 -1
- package/dist/_esm5.processed/parsers/manifest/utils/get_last_time_from_adaptation.js +1 -1
- package/dist/_esm5.processed/transports/metaplaylist/pipelines.js +0 -2
- package/dist/_esm5.processed/transports/smooth/segment_loader.js +1 -1
- package/dist/_esm5.processed/transports/types.d.ts +1 -1
- package/dist/_esm5.processed/utils/deep_merge.d.ts +1 -1
- package/dist/_esm5.processed/utils/deep_merge.js +6 -5
- package/dist/_esm5.processed/utils/task_canceller.d.ts +0 -3
- package/dist/_esm5.processed/utils/task_canceller.js +0 -3
- package/dist/mpd-parser.wasm +0 -0
- package/dist/rx-player.js +1390 -1059
- package/dist/rx-player.min.js +1 -1
- package/jest.config.js +5 -0
- package/package.json +31 -30
- package/sonar-project.properties +1 -1
- package/src/compat/eme/load_session.ts +5 -6
- package/src/compat/event_listeners.ts +5 -0
- package/src/core/api/media_element_track_choice_manager.ts +1 -1
- package/src/core/api/public_api.ts +2 -2
- package/src/core/decrypt/create_session.ts +28 -2
- package/src/core/decrypt/utils/loaded_sessions_store.ts +29 -0
- package/src/core/fetchers/segment/segment_fetcher.ts +1 -1
- package/src/core/init/content_time_boundaries_observer.ts +116 -42
- package/src/core/stream/representation/check_for_discontinuity.ts +28 -10
- package/src/core/stream/representation/get_buffer_status.ts +27 -34
- package/src/errors/encrypted_media_error.ts +1 -2
- package/src/errors/error_codes.ts +2 -2
- package/src/errors/media_error.ts +1 -2
- package/src/errors/network_error.ts +1 -2
- package/src/errors/other_error.ts +1 -2
- package/src/manifest/__tests__/adaptation.test.ts +4 -3
- package/src/manifest/__tests__/representation.test.ts +4 -3
- package/src/manifest/manifest.ts +1 -1
- package/src/manifest/representation_index/__tests__/static.test.ts +5 -4
- package/src/manifest/representation_index/static.ts +28 -9
- package/src/manifest/representation_index/types.ts +62 -46
- package/src/parsers/manifest/dash/common/indexes/base.ts +27 -11
- package/src/parsers/manifest/dash/common/indexes/list.ts +32 -15
- package/src/parsers/manifest/dash/common/indexes/template.ts +73 -27
- package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +60 -8
- package/src/parsers/manifest/dash/common/indexes/{is_period_fulfilled.ts → utils.ts} +4 -13
- package/src/parsers/manifest/dash/common/parse_periods.ts +1 -1
- package/src/parsers/manifest/local/parse_local_manifest.ts +8 -20
- package/src/parsers/manifest/local/representation_index.ts +51 -16
- package/src/parsers/manifest/local/types.ts +13 -0
- package/src/parsers/manifest/metaplaylist/representation_index.ts +31 -11
- package/src/parsers/manifest/smooth/create_parser.ts +4 -4
- package/src/parsers/manifest/smooth/representation_index.ts +40 -15
- package/src/parsers/manifest/utils/__tests__/get_first_time_from_adaptations.test.ts +4 -3
- package/src/parsers/manifest/utils/__tests__/get_last_time_from_adaptation.test.ts +4 -3
- package/src/parsers/manifest/utils/get_first_time_from_adaptation.ts +1 -1
- package/src/parsers/manifest/utils/get_last_time_from_adaptation.ts +1 -1
- package/src/transports/metaplaylist/pipelines.ts +0 -2
- package/src/transports/smooth/segment_loader.ts +1 -1
- package/src/transports/types.ts +1 -1
- package/src/utils/__tests__/initialization_segment_cache.test.ts +7 -0
- package/src/utils/deep_merge.ts +7 -4
- package/src/utils/task_canceller.ts +0 -3
package/jest.config.js
CHANGED
|
@@ -6,6 +6,11 @@ module.exports = {
|
|
|
6
6
|
roots: ["<rootDir>/src"],
|
|
7
7
|
preset: "ts-jest",
|
|
8
8
|
testEnvironment: "jsdom",
|
|
9
|
+
// Without this, Jest just fails when importing rxjs, for some arcane
|
|
10
|
+
// ESM-vs-CommonJS reasons linked to how it works internally.
|
|
11
|
+
moduleNameMapper: {
|
|
12
|
+
'^rxjs$': require.resolve('rxjs'),
|
|
13
|
+
},
|
|
9
14
|
testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"],
|
|
10
15
|
collectCoverageFrom: [
|
|
11
16
|
"src/**/*.ts",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rx-player",
|
|
3
3
|
"author": "Canal+",
|
|
4
|
-
"version": "3.28.
|
|
4
|
+
"version": "3.28.1-dev.2022083000",
|
|
5
5
|
"description": "Canal+ HTML5 Video Player",
|
|
6
6
|
"main": "./dist/rx-player.js",
|
|
7
7
|
"keywords": [
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"doc": "rm -rf doc/generated; node ./scripts/doc-generator/index.js doc/ doc/generated \"$(cat VERSION)\"",
|
|
49
49
|
"lint": "eslint src -c .eslintrc.js",
|
|
50
50
|
"lint:demo": "eslint demo/full/scripts",
|
|
51
|
-
"lint:tests": "eslint tests/**/*.js",
|
|
51
|
+
"lint:tests": "eslint tests/**/*.js --ignore-pattern '/tests/performance/bundle*'",
|
|
52
52
|
"list": "node scripts/list-npm-scripts.js",
|
|
53
53
|
"prepublishOnly": "npm run build:modular",
|
|
54
54
|
"standalone": "node ./scripts/run_standalone_demo.js",
|
|
@@ -81,34 +81,35 @@
|
|
|
81
81
|
"rxjs": "7.4.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@babel/core": "7.18.
|
|
85
|
-
"@babel/plugin-transform-runtime": "7.18.
|
|
86
|
-
"@babel/preset-env": "7.18.
|
|
87
|
-
"@babel/preset-react": "7.
|
|
88
|
-
"@types/chai": "4.3.
|
|
89
|
-
"@types/jest": "
|
|
84
|
+
"@babel/core": "7.18.13",
|
|
85
|
+
"@babel/plugin-transform-runtime": "7.18.10",
|
|
86
|
+
"@babel/preset-env": "7.18.10",
|
|
87
|
+
"@babel/preset-react": "7.18.6",
|
|
88
|
+
"@types/chai": "4.3.3",
|
|
89
|
+
"@types/jest": "28.1.8",
|
|
90
90
|
"@types/mocha": "9.1.1",
|
|
91
|
-
"@types/node": "
|
|
92
|
-
"@types/sinon": "10.0.
|
|
93
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
94
|
-
"@typescript-eslint/eslint-plugin-tslint": "5.
|
|
95
|
-
"@typescript-eslint/parser": "5.
|
|
91
|
+
"@types/node": "18.7.13",
|
|
92
|
+
"@types/sinon": "10.0.13",
|
|
93
|
+
"@typescript-eslint/eslint-plugin": "5.35.1",
|
|
94
|
+
"@typescript-eslint/eslint-plugin-tslint": "5.35.1",
|
|
95
|
+
"@typescript-eslint/parser": "5.35.1",
|
|
96
96
|
"arraybuffer-loader": "1.0.8",
|
|
97
97
|
"babel-loader": "8.2.5",
|
|
98
98
|
"chai": "4.3.6",
|
|
99
|
-
"cheerio": "1.0.0-rc.
|
|
100
|
-
"core-js": "3.
|
|
101
|
-
"esbuild": "0.
|
|
102
|
-
"eslint": "8.
|
|
99
|
+
"cheerio": "1.0.0-rc.12",
|
|
100
|
+
"core-js": "3.25.0",
|
|
101
|
+
"esbuild": "0.15.5",
|
|
102
|
+
"eslint": "8.23.0",
|
|
103
103
|
"eslint-plugin-import": "2.26.0",
|
|
104
|
-
"eslint-plugin-jsdoc": "39.3.
|
|
105
|
-
"eslint-plugin-react": "7.
|
|
104
|
+
"eslint-plugin-jsdoc": "39.3.6",
|
|
105
|
+
"eslint-plugin-react": "7.31.1",
|
|
106
106
|
"esm": "3.2.25",
|
|
107
107
|
"express": "4.18.1",
|
|
108
|
-
"highlight.js": "11.
|
|
108
|
+
"highlight.js": "11.6.0",
|
|
109
109
|
"html-entities": "2.3.3",
|
|
110
|
-
"jest": "
|
|
111
|
-
"
|
|
110
|
+
"jest": "28.1.3",
|
|
111
|
+
"jest-environment-jsdom": "28.1.3",
|
|
112
|
+
"karma": "6.4.0",
|
|
112
113
|
"karma-chrome-launcher": "3.1.1",
|
|
113
114
|
"karma-firefox-launcher": "2.1.2",
|
|
114
115
|
"karma-mocha": "2.0.1",
|
|
@@ -117,19 +118,19 @@
|
|
|
117
118
|
"mocha": "10.0.0",
|
|
118
119
|
"mocha-loader": "5.1.5",
|
|
119
120
|
"raw-loader": "4.0.2",
|
|
120
|
-
"react": "18.
|
|
121
|
-
"react-dom": "18.
|
|
121
|
+
"react": "18.2.0",
|
|
122
|
+
"react-dom": "18.2.0",
|
|
122
123
|
"regenerator-runtime": "0.13.9",
|
|
123
124
|
"rimraf": "3.0.2",
|
|
124
125
|
"semver": "7.3.7",
|
|
125
126
|
"sinon": "14.0.0",
|
|
126
|
-
"terser-webpack-plugin": "5.3.
|
|
127
|
-
"ts-jest": "
|
|
128
|
-
"ts-loader": "9.3.
|
|
127
|
+
"terser-webpack-plugin": "5.3.5",
|
|
128
|
+
"ts-jest": "28.0.8",
|
|
129
|
+
"ts-loader": "9.3.1",
|
|
129
130
|
"tslint": "6.1.3",
|
|
130
|
-
"typescript": "4.
|
|
131
|
-
"webpack": "5.
|
|
132
|
-
"webpack-bundle-analyzer": "4.
|
|
131
|
+
"typescript": "4.8.2",
|
|
132
|
+
"webpack": "5.74.0",
|
|
133
|
+
"webpack-bundle-analyzer": "4.6.1",
|
|
133
134
|
"webpack-cli": "4.10.0"
|
|
134
135
|
},
|
|
135
136
|
"scripts-list": {
|
package/sonar-project.properties
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
sonar.projectKey=rx-player
|
|
2
2
|
sonar.organization=rx-player
|
|
3
3
|
sonar.projectName=rx-player
|
|
4
|
-
sonar.projectVersion=3.28.
|
|
4
|
+
sonar.projectVersion=3.28.1-dev.2022083000
|
|
5
5
|
sonar.sources=./src,./demo,./tests
|
|
6
6
|
sonar.exclusions=demo/full/bundle.js,demo/standalone/lib.js,demo/bundle.js
|
|
7
7
|
sonar.host.url=https://sonarcloud.io
|
|
@@ -23,15 +23,14 @@ const EME_WAITING_DELAY_LOADED_SESSION_EMPTY_KEYSTATUSES = 100;
|
|
|
23
23
|
* Load a persistent session, based on its `sessionId`, on the given
|
|
24
24
|
* MediaKeySession.
|
|
25
25
|
*
|
|
26
|
-
* Returns
|
|
27
|
-
* - true if the persistent MediaKeySession was found and loaded
|
|
28
|
-
* - false if no persistent MediaKeySession was found with that `sessionId`.
|
|
29
|
-
* Then completes.
|
|
26
|
+
* Returns a Promise which resolves with:
|
|
27
|
+
* - `true` if the persistent MediaKeySession was found and loaded
|
|
28
|
+
* - `false` if no persistent MediaKeySession was found with that `sessionId`.
|
|
30
29
|
*
|
|
31
|
-
* The
|
|
30
|
+
* The Promise rejects if anything goes wrong in the process.
|
|
32
31
|
* @param {MediaKeySession} session
|
|
33
32
|
* @param {string} sessionId
|
|
34
|
-
* @returns {
|
|
33
|
+
* @returns {Promise.<boolean>}
|
|
35
34
|
*/
|
|
36
35
|
export default async function loadSession(
|
|
37
36
|
session : MediaKeySession | ICustomMediaKeySession,
|
|
@@ -205,11 +205,13 @@ function getPageActivityRef(
|
|
|
205
205
|
const ref = createSharedReference(true);
|
|
206
206
|
stopListening.register(() => {
|
|
207
207
|
clearTimeout(currentTimeout);
|
|
208
|
+
currentTimeout = undefined;
|
|
208
209
|
ref.finish();
|
|
209
210
|
});
|
|
210
211
|
|
|
211
212
|
isDocVisibleRef.onUpdate(function onDocVisibilityChange(isVisible : boolean) : void {
|
|
212
213
|
clearTimeout(currentTimeout); // clear potential previous timeout
|
|
214
|
+
currentTimeout = undefined;
|
|
213
215
|
if (!isVisible) {
|
|
214
216
|
const { INACTIVITY_DELAY } = config.getCurrent();
|
|
215
217
|
currentTimeout = window.setTimeout(() => {
|
|
@@ -313,6 +315,7 @@ function getVideoVisibilityRef(
|
|
|
313
315
|
const ref = createSharedReference(true);
|
|
314
316
|
stopListening.register(() => {
|
|
315
317
|
clearTimeout(currentTimeout);
|
|
318
|
+
currentTimeout = undefined;
|
|
316
319
|
ref.finish();
|
|
317
320
|
});
|
|
318
321
|
|
|
@@ -324,6 +327,8 @@ function getVideoVisibilityRef(
|
|
|
324
327
|
return ref;
|
|
325
328
|
|
|
326
329
|
function checkCurrentVisibility() : void {
|
|
330
|
+
clearTimeout(currentTimeout);
|
|
331
|
+
currentTimeout = undefined;
|
|
327
332
|
if (pipStatus.getValue().isEnabled || isDocVisibleRef.getValue()) {
|
|
328
333
|
ref.setValueIfChanged(true);
|
|
329
334
|
} else {
|
|
@@ -102,7 +102,7 @@ function createAudioTracks(
|
|
|
102
102
|
const track = { language: audioTrack.language,
|
|
103
103
|
id,
|
|
104
104
|
normalized: normalizeLanguage(audioTrack.language),
|
|
105
|
-
audioDescription:
|
|
105
|
+
audioDescription: audioTrack.kind === "descriptions",
|
|
106
106
|
representations: [] as Representation[] };
|
|
107
107
|
newAudioTracks.push({ track,
|
|
108
108
|
nativeTrack: audioTrack });
|
|
@@ -432,7 +432,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
432
432
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
|
|
433
433
|
videoElement.preload = "auto";
|
|
434
434
|
|
|
435
|
-
this.version = /* PLAYER_VERSION */"3.28.
|
|
435
|
+
this.version = /* PLAYER_VERSION */"3.28.1-dev.2022083000";
|
|
436
436
|
this.log = log;
|
|
437
437
|
this.state = "STOPPED";
|
|
438
438
|
this.videoElement = videoElement;
|
|
@@ -2943,7 +2943,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2943
2943
|
return activeRepresentations[currentPeriod.id];
|
|
2944
2944
|
}
|
|
2945
2945
|
}
|
|
2946
|
-
Player.version = /* PLAYER_VERSION */"3.28.
|
|
2946
|
+
Player.version = /* PLAYER_VERSION */"3.28.1-dev.2022083000";
|
|
2947
2947
|
|
|
2948
2948
|
/** Every events sent by the RxPlayer's public API. */
|
|
2949
2949
|
interface IPublicAPIEvent {
|
|
@@ -117,8 +117,18 @@ async function createAndTryToRetrievePersistentSession(
|
|
|
117
117
|
if (!hasLoadedSession) {
|
|
118
118
|
log.warn("DRM: No data stored for the loaded session");
|
|
119
119
|
persistentSessionsStore.delete(storedEntry.sessionId);
|
|
120
|
+
|
|
121
|
+
// The EME specification is kind of implicit about it but it seems from my
|
|
122
|
+
// understanding (Paul B.) that a MediaKeySession on wich a `load` attempt
|
|
123
|
+
// did not succeed due to the loaded session not being found by the
|
|
124
|
+
// browser/CDM, should neither be used anymore nor closed.
|
|
125
|
+
// Thus, we're creating another `"persistent-license"` `MediaKeySession`
|
|
126
|
+
// in that specific case.
|
|
127
|
+
loadedSessionsStore.removeSessionWithoutClosingIt(entry.mediaKeySession);
|
|
128
|
+
const newEntry = loadedSessionsStore.createSession(initData,
|
|
129
|
+
"persistent-license");
|
|
120
130
|
return { type: MediaKeySessionLoadingType.Created,
|
|
121
|
-
value:
|
|
131
|
+
value: newEntry };
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
if (hasLoadedSession && isSessionUsable(entry.mediaKeySession)) {
|
|
@@ -153,7 +163,23 @@ async function createAndTryToRetrievePersistentSession(
|
|
|
153
163
|
persistentSessionsStore.delete(persistentEntry.sessionId);
|
|
154
164
|
}
|
|
155
165
|
|
|
156
|
-
|
|
166
|
+
try {
|
|
167
|
+
await loadedSessionsStore.closeSession(entry.mediaKeySession);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
// From reading the EME specification in details, it seems that a
|
|
170
|
+
// `MediaKeySession`'s ability to be closed is tightly linked to its
|
|
171
|
+
// possession of a "sanitized session ID" set as `sessionId`.
|
|
172
|
+
// This is never clearly stated however and I'm (Paul B.) always afraid of
|
|
173
|
+
// breaking compatibility when it comes to EME code.
|
|
174
|
+
// So we still try to close the `MediaKeySession` in any case, only, if it
|
|
175
|
+
// fails and it didn't had any `sessionId` set, we just ignore the error.
|
|
176
|
+
// Note that trying to close the `MediaKeySession` might incur some delays
|
|
177
|
+
// in those rare cases.
|
|
178
|
+
if (entry.mediaKeySession.sessionId !== "") {
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
loadedSessionsStore.removeSessionWithoutClosingIt(entry.mediaKeySession);
|
|
182
|
+
}
|
|
157
183
|
if (cancelSignal.cancellationError !== null) {
|
|
158
184
|
throw cancelSignal.cancellationError;
|
|
159
185
|
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
loadSession,
|
|
23
23
|
} from "../../../compat";
|
|
24
24
|
import log from "../../../log";
|
|
25
|
+
import assert from "../../../utils/assert";
|
|
25
26
|
import isNullOrUndefined from "../../../utils/is_null_or_undefined";
|
|
26
27
|
import { IProcessedProtectionData } from "../types";
|
|
27
28
|
import KeySessionRecord from "./key_session_record";
|
|
@@ -310,6 +311,34 @@ export default class LoadedSessionsStore {
|
|
|
310
311
|
await Promise.all(closingProms);
|
|
311
312
|
}
|
|
312
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Find the given `MediaKeySession` in the `LoadedSessionsStore` and removes
|
|
316
|
+
* any reference to it without actually closing it.
|
|
317
|
+
*
|
|
318
|
+
* Returns `true` if the given `mediaKeySession` has been found and removed,
|
|
319
|
+
* `false` otherwise.
|
|
320
|
+
*
|
|
321
|
+
* Note that this may create a `MediaKeySession` leakage in the wrong
|
|
322
|
+
* conditions, cases where this method should be called should be very
|
|
323
|
+
* carefully evaluated.
|
|
324
|
+
* @param {MediaKeySession} mediaKeySession
|
|
325
|
+
* @returns {boolean}
|
|
326
|
+
*/
|
|
327
|
+
public removeSessionWithoutClosingIt(
|
|
328
|
+
mediaKeySession : MediaKeySession | ICustomMediaKeySession
|
|
329
|
+
) : boolean {
|
|
330
|
+
assert(mediaKeySession.sessionId === "",
|
|
331
|
+
"Initialized `MediaKeySession`s should always be properly closed");
|
|
332
|
+
for (let i = this._storage.length - 1; i >= 0; i--) {
|
|
333
|
+
const stored = this._storage[i];
|
|
334
|
+
if (stored.mediaKeySession === mediaKeySession) {
|
|
335
|
+
this._storage.splice(i, 1);
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
313
342
|
/**
|
|
314
343
|
* Get the index of a stored MediaKeySession entry based on its
|
|
315
344
|
* `KeySessionRecord`.
|
|
@@ -61,7 +61,7 @@ const generateRequestID = idGenerator();
|
|
|
61
61
|
* `options` argument, which may retry a segment request when it fails.
|
|
62
62
|
*
|
|
63
63
|
* @param {string} bufferType
|
|
64
|
-
* @param {Object}
|
|
64
|
+
* @param {Object} pipeline
|
|
65
65
|
* @param {Object} callbacks
|
|
66
66
|
* @param {Object} options
|
|
67
67
|
* @returns {Function}
|
|
@@ -31,6 +31,7 @@ import Manifest, {
|
|
|
31
31
|
} from "../../manifest";
|
|
32
32
|
import { fromEvent } from "../../utils/event_emitter";
|
|
33
33
|
import filterMap from "../../utils/filter_map";
|
|
34
|
+
import isNullOrUndefined from "../../utils/is_null_or_undefined";
|
|
34
35
|
import createSharedReference from "../../utils/reference";
|
|
35
36
|
import { IReadOnlyPlaybackObserver } from "../api";
|
|
36
37
|
import {
|
|
@@ -86,7 +87,7 @@ export default function ContentTimeBoundariesObserver(
|
|
|
86
87
|
"earliest time announced in the Manifest.");
|
|
87
88
|
return EVENTS.warning(warning);
|
|
88
89
|
} else if (
|
|
89
|
-
wantedPosition > maximumPositionCalculator.
|
|
90
|
+
wantedPosition > maximumPositionCalculator.getMaximumAvailablePosition()
|
|
90
91
|
) {
|
|
91
92
|
const warning = new MediaError("MEDIA_TIME_AFTER_MANIFEST",
|
|
92
93
|
"The current position is after the latest " +
|
|
@@ -106,13 +107,10 @@ export default function ContentTimeBoundariesObserver(
|
|
|
106
107
|
const updateDurationOnManifestUpdate$ = fromEvent(manifest, "manifestUpdate").pipe(
|
|
107
108
|
startWith(null),
|
|
108
109
|
tap(() => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// TODO handle finished dynamic contents?
|
|
114
|
-
contentDuration.setValue(undefined);
|
|
115
|
-
}
|
|
110
|
+
const duration = manifest.isDynamic ?
|
|
111
|
+
maximumPositionCalculator.getEndingPosition() :
|
|
112
|
+
maximumPositionCalculator.getMaximumAvailablePosition();
|
|
113
|
+
contentDuration.setValue(duration);
|
|
116
114
|
}),
|
|
117
115
|
ignoreElements()
|
|
118
116
|
);
|
|
@@ -120,24 +118,23 @@ export default function ContentTimeBoundariesObserver(
|
|
|
120
118
|
const updateDurationAndTimeBoundsOnTrackChange$ = streams.pipe(
|
|
121
119
|
tap((message) => { // Update Manifest's bounds and duration if necessary
|
|
122
120
|
if (message.type === "adaptationChange") {
|
|
121
|
+
if (!manifest.isLastPeriodKnown) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
123
124
|
const lastPeriod = manifest.periods[manifest.periods.length - 1];
|
|
124
125
|
if (message.value.period.id === lastPeriod?.id) {
|
|
125
|
-
if (message.value.type === "audio") {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
} else if (message.value.type === "video") {
|
|
134
|
-
maximumPositionCalculator
|
|
135
|
-
.updateLastVideoAdaptation(message.value.adaptation);
|
|
136
|
-
if (!manifest.isDynamic) {
|
|
137
|
-
contentDuration.setValue(
|
|
138
|
-
maximumPositionCalculator.getCurrentMaximumPosition()
|
|
139
|
-
);
|
|
126
|
+
if (message.value.type === "audio" || message.value.type === "video") {
|
|
127
|
+
if (message.value.type === "audio") {
|
|
128
|
+
maximumPositionCalculator
|
|
129
|
+
.updateLastAudioAdaptation(message.value.adaptation);
|
|
130
|
+
} else {
|
|
131
|
+
maximumPositionCalculator
|
|
132
|
+
.updateLastVideoAdaptation(message.value.adaptation);
|
|
140
133
|
}
|
|
134
|
+
const newDuration = manifest.isDynamic ?
|
|
135
|
+
maximumPositionCalculator.getMaximumAvailablePosition() :
|
|
136
|
+
maximumPositionCalculator.getEndingPosition();
|
|
137
|
+
contentDuration.setValue(newDuration);
|
|
141
138
|
}
|
|
142
139
|
}
|
|
143
140
|
}
|
|
@@ -182,7 +179,7 @@ class MaximumPositionCalculator {
|
|
|
182
179
|
* If no Adaptation has been set, it should be set to `null`.
|
|
183
180
|
*
|
|
184
181
|
* Allows to calculate the maximum position more precizely in
|
|
185
|
-
* `
|
|
182
|
+
* `getMaximumAvailablePosition` and `getEndingPosition`.
|
|
186
183
|
* @param {Object|null} adaptation
|
|
187
184
|
*/
|
|
188
185
|
public updateLastAudioAdaptation(adaptation : Adaptation | null) : void {
|
|
@@ -194,7 +191,7 @@ class MaximumPositionCalculator {
|
|
|
194
191
|
* If no Adaptation has been set, it should be set to `null`.
|
|
195
192
|
*
|
|
196
193
|
* Allows to calculate the maximum position more precizely in
|
|
197
|
-
* `
|
|
194
|
+
* `getMaximumAvailablePosition` and `getEndingPosition`.
|
|
198
195
|
* @param {Object|null} adaptation
|
|
199
196
|
*/
|
|
200
197
|
public updateLastVideoAdaptation(adaptation : Adaptation | null) : void {
|
|
@@ -202,11 +199,11 @@ class MaximumPositionCalculator {
|
|
|
202
199
|
}
|
|
203
200
|
|
|
204
201
|
/**
|
|
205
|
-
* Returns an estimate of the maximum position reachable
|
|
206
|
-
* circumstances.
|
|
202
|
+
* Returns an estimate of the maximum position currently reachable (i.e.
|
|
203
|
+
* segments are available) under the current circumstances.
|
|
207
204
|
* @returns {number}
|
|
208
205
|
*/
|
|
209
|
-
public
|
|
206
|
+
public getMaximumAvailablePosition() : number {
|
|
210
207
|
if (this._manifest.isDynamic) {
|
|
211
208
|
return this._manifest.getLivePosition() ??
|
|
212
209
|
this._manifest.getMaximumSafePosition();
|
|
@@ -220,7 +217,7 @@ class MaximumPositionCalculator {
|
|
|
220
217
|
return this._manifest.getMaximumSafePosition();
|
|
221
218
|
} else {
|
|
222
219
|
const lastVideoPosition =
|
|
223
|
-
|
|
220
|
+
getLastAvailablePositionFromAdaptation(this._lastVideoAdaptation);
|
|
224
221
|
if (typeof lastVideoPosition !== "number") {
|
|
225
222
|
return this._manifest.getMaximumSafePosition();
|
|
226
223
|
}
|
|
@@ -228,16 +225,16 @@ class MaximumPositionCalculator {
|
|
|
228
225
|
}
|
|
229
226
|
} else if (this._lastVideoAdaptation === null) {
|
|
230
227
|
const lastAudioPosition =
|
|
231
|
-
|
|
228
|
+
getLastAvailablePositionFromAdaptation(this._lastAudioAdaptation);
|
|
232
229
|
if (typeof lastAudioPosition !== "number") {
|
|
233
230
|
return this._manifest.getMaximumSafePosition();
|
|
234
231
|
}
|
|
235
232
|
return lastAudioPosition;
|
|
236
233
|
} else {
|
|
237
|
-
const lastAudioPosition =
|
|
234
|
+
const lastAudioPosition = getLastAvailablePositionFromAdaptation(
|
|
238
235
|
this._lastAudioAdaptation
|
|
239
236
|
);
|
|
240
|
-
const lastVideoPosition =
|
|
237
|
+
const lastVideoPosition = getLastAvailablePositionFromAdaptation(
|
|
241
238
|
this._lastVideoAdaptation
|
|
242
239
|
);
|
|
243
240
|
if (typeof lastAudioPosition !== "number" ||
|
|
@@ -249,20 +246,59 @@ class MaximumPositionCalculator {
|
|
|
249
246
|
}
|
|
250
247
|
}
|
|
251
248
|
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Returns an estimate of the actual ending position once
|
|
252
|
+
* the full content is available.
|
|
253
|
+
* Returns `undefined` if that could not be determined, for various reasons.
|
|
254
|
+
* @returns {number|undefined}
|
|
255
|
+
*/
|
|
256
|
+
public getEndingPosition() : number | undefined {
|
|
257
|
+
if (!this._manifest.isDynamic) {
|
|
258
|
+
return this.getMaximumAvailablePosition();
|
|
259
|
+
}
|
|
260
|
+
if (this._lastVideoAdaptation === undefined ||
|
|
261
|
+
this._lastAudioAdaptation === undefined)
|
|
262
|
+
{
|
|
263
|
+
return undefined;
|
|
264
|
+
} else if (this._lastAudioAdaptation === null) {
|
|
265
|
+
if (this._lastVideoAdaptation === null) {
|
|
266
|
+
return undefined;
|
|
267
|
+
} else {
|
|
268
|
+
return getEndingPositionFromAdaptation(this._lastVideoAdaptation) ??
|
|
269
|
+
undefined;
|
|
270
|
+
}
|
|
271
|
+
} else if (this._lastVideoAdaptation === null) {
|
|
272
|
+
return getEndingPositionFromAdaptation(this._lastAudioAdaptation) ??
|
|
273
|
+
undefined;
|
|
274
|
+
} else {
|
|
275
|
+
const lastAudioPosition =
|
|
276
|
+
getEndingPositionFromAdaptation(this._lastAudioAdaptation);
|
|
277
|
+
const lastVideoPosition =
|
|
278
|
+
getEndingPositionFromAdaptation(this._lastVideoAdaptation);
|
|
279
|
+
if (typeof lastAudioPosition !== "number" ||
|
|
280
|
+
typeof lastVideoPosition !== "number")
|
|
281
|
+
{
|
|
282
|
+
return undefined;
|
|
283
|
+
} else {
|
|
284
|
+
return Math.min(lastAudioPosition, lastVideoPosition);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
252
288
|
}
|
|
253
289
|
|
|
254
290
|
/**
|
|
255
|
-
* Returns
|
|
291
|
+
* Returns last currently available position from the Adaptation given.
|
|
256
292
|
* `undefined` if a time could not be found.
|
|
257
|
-
*
|
|
293
|
+
* `null` if the Adaptation has no segments (it could be that it didn't started or
|
|
258
294
|
* that it already finished for example).
|
|
259
295
|
*
|
|
260
|
-
* We consider the earliest last
|
|
261
|
-
*
|
|
296
|
+
* We consider the earliest last available position from every Representation
|
|
297
|
+
* in the given Adaptation.
|
|
262
298
|
* @param {Object} adaptation
|
|
263
299
|
* @returns {Number|undefined|null}
|
|
264
300
|
*/
|
|
265
|
-
function
|
|
301
|
+
function getLastAvailablePositionFromAdaptation(
|
|
266
302
|
adaptation: Adaptation
|
|
267
303
|
) : number | undefined | null {
|
|
268
304
|
const { representations } = adaptation;
|
|
@@ -278,18 +314,56 @@ function getLastPositionFromAdaptation(
|
|
|
278
314
|
for (let i = 0; i < representations.length; i++) {
|
|
279
315
|
if (representations[i].index !== lastIndex) {
|
|
280
316
|
lastIndex = representations[i].index;
|
|
281
|
-
const lastPosition = representations[i].index.
|
|
317
|
+
const lastPosition = representations[i].index.getLastAvailablePosition();
|
|
282
318
|
if (lastPosition === undefined) { // we cannot tell
|
|
283
319
|
return undefined;
|
|
284
320
|
}
|
|
285
321
|
if (lastPosition !== null) {
|
|
286
|
-
min = min
|
|
287
|
-
|
|
322
|
+
min = isNullOrUndefined(min) ? lastPosition :
|
|
323
|
+
Math.min(min, lastPosition);
|
|
288
324
|
}
|
|
289
325
|
}
|
|
290
326
|
}
|
|
291
|
-
|
|
292
|
-
|
|
327
|
+
return min;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Returns ending time from the Adaptation given, once all its segments are
|
|
332
|
+
* available.
|
|
333
|
+
* `undefined` if a time could not be found.
|
|
334
|
+
* `null` if the Adaptation has no segments (it could be that it already
|
|
335
|
+
* finished for example).
|
|
336
|
+
*
|
|
337
|
+
* We consider the earliest ending time from every Representation in the given
|
|
338
|
+
* Adaptation.
|
|
339
|
+
* @param {Object} adaptation
|
|
340
|
+
* @returns {Number|undefined|null}
|
|
341
|
+
*/
|
|
342
|
+
function getEndingPositionFromAdaptation(
|
|
343
|
+
adaptation: Adaptation
|
|
344
|
+
) : number | undefined | null {
|
|
345
|
+
const { representations } = adaptation;
|
|
346
|
+
let min : null | number = null;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Some Manifest parsers use the exact same `IRepresentationIndex` reference
|
|
350
|
+
* for each Representation of a given Adaptation, because in the actual source
|
|
351
|
+
* Manifest file, indexing data is often defined at Adaptation-level.
|
|
352
|
+
* This variable allows to optimize the logic here when this is the case.
|
|
353
|
+
*/
|
|
354
|
+
let lastIndex : IRepresentationIndex | undefined;
|
|
355
|
+
for (let i = 0; i < representations.length; i++) {
|
|
356
|
+
if (representations[i].index !== lastIndex) {
|
|
357
|
+
lastIndex = representations[i].index;
|
|
358
|
+
const lastPosition = representations[i].index.getEnd();
|
|
359
|
+
if (lastPosition === undefined) { // we cannot tell
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
if (lastPosition !== null) {
|
|
363
|
+
min = isNullOrUndefined(min) ? lastPosition :
|
|
364
|
+
Math.min(min, lastPosition);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
293
367
|
}
|
|
294
368
|
return min;
|
|
295
369
|
}
|
|
@@ -102,10 +102,17 @@ export default function checkForDiscontinuity(
|
|
|
102
102
|
(nextSegmentStart === null ||
|
|
103
103
|
nextBufferedSegment.infos.segment.end <= nextSegmentStart)
|
|
104
104
|
) {
|
|
105
|
+
const discontinuityEnd = nextBufferedSegment.bufferedStart;
|
|
106
|
+
if (!hasFinishedLoading &&
|
|
107
|
+
representation.index.awaitSegmentBetween(checkedRange.start,
|
|
108
|
+
discontinuityEnd) !== false)
|
|
109
|
+
{
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
105
112
|
log.debug("RS: current discontinuity encountered",
|
|
106
113
|
adaptation.type, nextBufferedSegment.bufferedStart);
|
|
107
114
|
return { start: undefined,
|
|
108
|
-
end:
|
|
115
|
+
end: discontinuityEnd };
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
// Check if there's a discontinuity BETWEEN segments of the current range
|
|
@@ -116,16 +123,27 @@ export default function checkForDiscontinuity(
|
|
|
116
123
|
|
|
117
124
|
// If there was a hole between two consecutives segments, and if this hole
|
|
118
125
|
// comes before the next segment to load, there is a discontinuity (that hole!)
|
|
119
|
-
if (nextHoleIdx !== null
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
if (nextHoleIdx !== null) {
|
|
127
|
+
const segmentInfoBeforeHole = bufferedSegments[nextHoleIdx - 1];
|
|
128
|
+
const segmentInfoAfterHole = bufferedSegments[nextHoleIdx];
|
|
129
|
+
|
|
130
|
+
if (nextSegmentStart === null ||
|
|
131
|
+
segmentInfoAfterHole.infos.segment.end <= nextSegmentStart)
|
|
132
|
+
{
|
|
133
|
+
if (!hasFinishedLoading && representation.index
|
|
134
|
+
.awaitSegmentBetween(segmentInfoBeforeHole.infos.segment.end,
|
|
135
|
+
segmentInfoAfterHole.infos.segment.time) !== false)
|
|
136
|
+
{
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const start = segmentInfoBeforeHole.bufferedEnd as number;
|
|
140
|
+
const end = segmentInfoAfterHole.bufferedStart as number;
|
|
141
|
+
log.debug("RS: future discontinuity encountered", adaptation.type, start, end);
|
|
142
|
+
return { start, end };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
127
145
|
|
|
128
|
-
|
|
146
|
+
if (nextSegmentStart === null) {
|
|
129
147
|
// If no hole between segments and no segment to load, check for a
|
|
130
148
|
// discontinuity at the end of the Period
|
|
131
149
|
|