@nomercy-entertainment/nomercy-video-player 2.2.1 → 2.2.2
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/CHANGELOG.md +8 -0
- package/dist/adapters/video-backend/html5.js +10 -1
- package/dist/adapters/video-backend/source-outage.d.ts +24 -0
- package/dist/adapters/video-backend/source-outage.js +30 -0
- package/dist/nomercy-video-player.iife.js +1 -1
- package/dist/plugins/desktop-ui/data/buttons.js +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.2.2] — 2026-09-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- A 404 got the full retry ladder: thirty seconds of spinner on an episode that was never encoded, ending in the generic network error. The file is not late, it is absent, and no amount of waiting changes the answer; 410 is the same answer said more firmly. Both now get no retries and their own code, `core:stream/media-absent`, so a consumer can offer the next item instead of the dead-end overlay it shows for a decode failure. The code matches `CoreErrorCodes.MEDIA_ABSENT` in the Kotlin trio.
|
|
10
|
+
- Georgia's subtitle-font fallback was `sans-serif`. Georgia is a serif face, so a client without it installed — Android carries none — fell back to the one font every other choice already draws, collapsing distinct options into each other.
|
|
11
|
+
|
|
12
|
+
|
|
5
13
|
## [2.2.1] — 2026-09-04
|
|
6
14
|
|
|
7
15
|
### Changed
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// SPDX-License-Identifier: Apache-2.0
|
|
7
7
|
// -----------------------------------------------------------------------------
|
|
8
8
|
import { abrCeiling, appendAuthTokenParam, createAuthorizationXhrSetup, destroyHlsInstance, detectDisplayHdr, hdrDecision, isHls, MediaElementBackend, MediaFormatError, mediaFormatError, panePixels, resetMediaElement, sizeAbrCeiling, supportsNativeHls, } from '@nomercy-entertainment/nomercy-player-core';
|
|
9
|
-
import { SOURCE_OUTAGE_BACKOFF_MS, sourceOutageRetryLimit } from './source-outage.js';
|
|
9
|
+
import { isMediaAbsentStatus, MEDIA_ABSENT, SOURCE_OUTAGE_BACKOFF_MS, sourceOutageRetryLimit } from './source-outage.js';
|
|
10
10
|
const CODEC_LABELS = [
|
|
11
11
|
[/avc1|h264/i, 'H.264'],
|
|
12
12
|
[/hvc1|hev1|h265/i, 'H.265 (HEVC)'],
|
|
@@ -860,6 +860,15 @@ export class Html5VideoBackend extends MediaElementBackend {
|
|
|
860
860
|
if (httpStatus === 0)
|
|
861
861
|
this._sawConnectionFailure = true;
|
|
862
862
|
const limit = sourceOutageRetryLimit(httpStatus, this._sawConnectionFailure);
|
|
863
|
+
// The server answered that this item has no media. Waiting cannot change
|
|
864
|
+
// that, and a consumer can act on it — offer the next item — but only if
|
|
865
|
+
// it is told which failure this was, so it gets its own code rather than
|
|
866
|
+
// the generic network one every outage ends with.
|
|
867
|
+
if (isMediaAbsentStatus(httpStatus) && !this._sawConnectionFailure) {
|
|
868
|
+
this._recoveringFromOutage = false;
|
|
869
|
+
this._escalateHlsError(MEDIA_ABSENT, `media absent (HTTP ${httpStatus}): ${data.details}`);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
863
872
|
if (this._netRetryCount >= limit) {
|
|
864
873
|
// The ladder is spent, but a device that gets a route back later
|
|
865
874
|
// still deserves its session rather than a dead error overlay.
|
|
@@ -45,9 +45,26 @@ export declare function sourceOutageBudgetMs(): number;
|
|
|
45
45
|
* connection-failure-gated budget would cut short.
|
|
46
46
|
*/
|
|
47
47
|
export declare function isOriginDownStatus(httpStatus: number): boolean;
|
|
48
|
+
/** No rung at all: the answer will not change by asking again. */
|
|
49
|
+
export declare const NO_RETRIES = 0;
|
|
50
|
+
/**
|
|
51
|
+
* True for a status that means the server answered, and answered that this
|
|
52
|
+
* media does not exist.
|
|
53
|
+
*
|
|
54
|
+
* An episode that has not been encoded yet answers 404, and it will answer 404
|
|
55
|
+
* to every retry: the file is not late, it is absent. Waiting is not a strategy
|
|
56
|
+
* for it, and the viewer is the only one who can decide what to do instead.
|
|
57
|
+
* 410 is the same answer said more firmly.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isMediaAbsentStatus(httpStatus: number): boolean;
|
|
48
60
|
/**
|
|
49
61
|
* How many rungs this failure is allowed.
|
|
50
62
|
*
|
|
63
|
+
* A 404 gets NONE. It used to get five, which is thirty seconds of spinner on
|
|
64
|
+
* an episode that was never encoded, and at the end of it the viewer was told
|
|
65
|
+
* nothing they could act on. A definite answer is worth surfacing the moment it
|
|
66
|
+
* arrives, so the consumer can offer to move to the next item.
|
|
67
|
+
*
|
|
51
68
|
* @param httpStatus - The status the load gave up on, or 0 when the failure
|
|
52
69
|
* carried no response at all, which is what a refused connection looks like
|
|
53
70
|
* from a browser.
|
|
@@ -56,3 +73,10 @@ export declare function isOriginDownStatus(httpStatus: number): boolean;
|
|
|
56
73
|
* from a video that is genuinely gone.
|
|
57
74
|
*/
|
|
58
75
|
export declare function sourceOutageRetryLimit(httpStatus: number, sawConnectionFailure: boolean): number;
|
|
76
|
+
/**
|
|
77
|
+
* The code a consumer reads to offer "skip to the next item" rather than a dead
|
|
78
|
+
* end. Its Kotlin peer is `CoreErrorCodes.MEDIA_ABSENT`; the two trios must
|
|
79
|
+
* name this the same failure or a client written against one is wrong on the
|
|
80
|
+
* other.
|
|
81
|
+
*/
|
|
82
|
+
export declare const MEDIA_ABSENT = "core:stream/media-absent";
|
|
@@ -63,9 +63,28 @@ export function isOriginDownStatus(httpStatus) {
|
|
|
63
63
|
|| httpStatus === 503
|
|
64
64
|
|| httpStatus === 504;
|
|
65
65
|
}
|
|
66
|
+
/** No rung at all: the answer will not change by asking again. */
|
|
67
|
+
export const NO_RETRIES = 0;
|
|
68
|
+
/**
|
|
69
|
+
* True for a status that means the server answered, and answered that this
|
|
70
|
+
* media does not exist.
|
|
71
|
+
*
|
|
72
|
+
* An episode that has not been encoded yet answers 404, and it will answer 404
|
|
73
|
+
* to every retry: the file is not late, it is absent. Waiting is not a strategy
|
|
74
|
+
* for it, and the viewer is the only one who can decide what to do instead.
|
|
75
|
+
* 410 is the same answer said more firmly.
|
|
76
|
+
*/
|
|
77
|
+
export function isMediaAbsentStatus(httpStatus) {
|
|
78
|
+
return httpStatus === 404 || httpStatus === 410;
|
|
79
|
+
}
|
|
66
80
|
/**
|
|
67
81
|
* How many rungs this failure is allowed.
|
|
68
82
|
*
|
|
83
|
+
* A 404 gets NONE. It used to get five, which is thirty seconds of spinner on
|
|
84
|
+
* an episode that was never encoded, and at the end of it the viewer was told
|
|
85
|
+
* nothing they could act on. A definite answer is worth surfacing the moment it
|
|
86
|
+
* arrives, so the consumer can offer to move to the next item.
|
|
87
|
+
*
|
|
69
88
|
* @param httpStatus - The status the load gave up on, or 0 when the failure
|
|
70
89
|
* carried no response at all, which is what a refused connection looks like
|
|
71
90
|
* from a browser.
|
|
@@ -74,11 +93,22 @@ export function isOriginDownStatus(httpStatus) {
|
|
|
74
93
|
* from a video that is genuinely gone.
|
|
75
94
|
*/
|
|
76
95
|
export function sourceOutageRetryLimit(httpStatus, sawConnectionFailure) {
|
|
96
|
+
// A connection failure first means the host went away; the 404 that follows
|
|
97
|
+
// is a route table still warming up, not a missing file.
|
|
77
98
|
if (sawConnectionFailure)
|
|
78
99
|
return SOURCE_OUTAGE_BACKOFF_MS.length;
|
|
79
100
|
if (isOriginDownStatus(httpStatus))
|
|
80
101
|
return SOURCE_OUTAGE_BACKOFF_MS.length;
|
|
102
|
+
if (isMediaAbsentStatus(httpStatus))
|
|
103
|
+
return NO_RETRIES;
|
|
81
104
|
if (httpStatus > 0)
|
|
82
105
|
return HTTP_STATUS_RETRY_LIMIT;
|
|
83
106
|
return SOURCE_OUTAGE_BACKOFF_MS.length;
|
|
84
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* The code a consumer reads to offer "skip to the next item" rather than a dead
|
|
110
|
+
* end. Its Kotlin peer is `CoreErrorCodes.MEDIA_ABSENT`; the two trios must
|
|
111
|
+
* name this the same failure or a client written against one is wrong on the
|
|
112
|
+
* other.
|
|
113
|
+
*/
|
|
114
|
+
export const MEDIA_ABSENT = 'core:stream/media-absent';
|