Skip to content

Commit 5e27460

Browse files
committed
Add the content length mismatch warning
1 parent 5b9d041 commit 5e27460

4 files changed

Lines changed: 84 additions & 0 deletions

File tree

index.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2049,6 +2049,8 @@ export interface MediaPlayerClass {
20492049

20502050
on(type: LogEvent['type'], listener: (e: LogEvent) => void, scope?: object): void;
20512051

2052+
on(type: FragmentContentLengthMismatch['type'], listener: (e: FragmentContentLengthMismatch) => void, scope?: object): void;
2053+
20522054
on(type: ManifestLoadedEvent['type'], listener: (e: ManifestLoadedEvent) => void, scope?: object): void;
20532055

20542056
on(type: MetricEvent['type'], listener: (e: MetricEvent) => void, scope?: object): void;
@@ -2432,6 +2434,7 @@ export interface MediaPlayerEvents {
24322434
KEY_SESSION_UPDATED: 'public_keySessionUpdated';
24332435
LICENSE_REQUEST_COMPLETE: 'public_licenseRequestComplete';
24342436
LICENSE_REQUEST_SENDING: 'public_licenseRequestSending';
2437+
FRAGMENT_CONTENT_LENGTH_MISMATCH: 'fragmentContentLengthMismatch';
24352438
LOG: 'log';
24362439
MANIFEST_LOADED: 'manifestLoaded';
24372440
MANIFEST_LOADING_STARTED: 'manifestLoadingStarted';
@@ -2712,6 +2715,14 @@ export interface LogEvent extends MediaPlayerEvent {
27122715
type: MediaPlayerEvents['LOG'];
27132716
}
27142717

2718+
export interface FragmentContentLengthMismatch extends MediaPlayerEvent {
2719+
type: MediaPlayerEvents['FRAGMENT_CONTENT_LENGTH_MISMATCH'];
2720+
responseUrl: string;
2721+
mediaType: string;
2722+
headerLength: number;
2723+
bodyLength: number;
2724+
}
2725+
27152726
export interface ManifestLoadedEvent extends MediaPlayerEvent {
27162727
data: object;
27172728
type: MediaPlayerEvents['MANIFEST_LOADED'];

src/streaming/MediaPlayerEvents.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,12 @@ class MediaPlayerEvents extends EventsBase {
493493
* @type {string}
494494
*/
495495
this.MANAGED_MEDIA_SOURCE_END_STREAMING = 'managedMediaSourceEndStreaming';
496+
497+
/**
498+
* Triggered when the 'Content-Length' header for a fragment does not match the byte length.
499+
* @event MediaPlayerEvents#FRAGMENT_CONTENT_LENGTH_MISMATCH
500+
*/
501+
this.FRAGMENT_CONTENT_LENGTH_MISMATCH = 'fragmentContentLengthMismatch';
496502
}
497503
}
498504

src/streaming/net/HTTPLoader.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import CommonAccessTokenController from '../controllers/CommonAccessTokenControl
4545
import ExtUrlQueryInfoController from '../controllers/ExtUrlQueryInfoController.js';
4646
import CommonMediaRequest from '../vo/CommonMediaRequest.js';
4747
import CommonMediaResponse from '../vo/CommonMediaResponse.js';
48+
import MediaPlayerEvents from '../MediaPlayerEvents.js';
4849

4950
/**
5051
* @module HTTPLoader
@@ -267,6 +268,15 @@ function HTTPLoader(cfg) {
267268
// GET requests still need a body since dash.js consumes it (manifests, segments).
268269
const hasUsableBody = !!commonMediaResponse.data || requestObject.method === HTTPRequest.POST;
269270
if (commonMediaResponse.status >= 200 && commonMediaResponse.status <= 299 && hasUsableBody) {
271+
if (hasContentLengthMismatch(commonMediaResponse)) {
272+
const responseUrl = commonMediaResponse.url;
273+
const mediaType = requestObject.mediaType
274+
const headerLength = commonMediaResponse.headers['content-length'];
275+
const bodyLength = commonMediaResponse.data.byteLength;
276+
277+
eventBus.trigger(MediaPlayerEvents.FRAGMENT_CONTENT_LENGTH_MISMATCH, { responseUrl, mediaType, headerLength, bodyLength });
278+
}
279+
270280
if (config.success) {
271281
config.success(commonMediaResponse.data, commonMediaResponse.statusText, commonMediaResponse.url);
272282
}
@@ -687,6 +697,19 @@ function HTTPLoader(cfg) {
687697
}
688698
}
689699

700+
function hasContentLengthMismatch(response) {
701+
if (response && response.data && response.headers) {
702+
const headerLength = response.headers['content-length'];
703+
const dataLength = response.data.byteLength;
704+
705+
if (headerLength && dataLength && Math.abs(dataLength - headerLength) > headerLength * 0.25) {
706+
return true;
707+
}
708+
}
709+
710+
return false;
711+
}
712+
690713
function reset() {
691714
httpRequests = [];
692715
delayedRequests = [];

test/unit/test/streaming/streaming.net.HTTPLoader.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {HTTPRequest} from '../../../../src/streaming/vo/metrics/HTTPRequest.js';
88
import Settings from '../../../../src/core/Settings.js';
99
import CmcdController from '../../../../src/streaming/controllers/CmcdController.js';
1010
import ClientDataReportingController from '../../../../src/streaming/controllers/ClientDataReportingController.js';
11+
import EventBus from '../../../../src/core/EventBus.js';
12+
import MediaPlayerEvents from '../../../../src/streaming/MediaPlayerEvents.js';
1113

1214
import {expect} from 'chai';
1315
import {fakeXhr} from 'nise';
@@ -203,6 +205,48 @@ describe('HTTPLoader', function () {
203205
requests[0].respond(200);
204206
});
205207

208+
it('Emits a FRAGMENT_CONTENT_LENGTH_MISMATCH event when Content-Length does not match actual length of response', async () => {
209+
const eventBus = EventBus(context).getInstance();
210+
const spy = sinon.spy();
211+
let resolveOnComplete;
212+
const completePromise = new Promise((resolve) => {
213+
resolveOnComplete = resolve;
214+
});
215+
216+
eventBus.on(MediaPlayerEvents.FRAGMENT_CONTENT_LENGTH_MISMATCH, spy, null);
217+
218+
const callbacks = _createCallbacks();
219+
callbacks.complete = sinon.spy(() => resolveOnComplete());
220+
221+
httpLoader = _createHttpLoader();
222+
const originalLoad = httpLoader.load;
223+
httpLoader.load = new Proxy(originalLoad, {
224+
apply(target, thisArg, args) {
225+
const result = Reflect.apply(target, thisArg, args);
226+
227+
result.then(() => {
228+
requests[0].respond(200, { 'content-length': 15 }, '0'.repeat(8));
229+
});
230+
231+
return result;
232+
}
233+
});
234+
235+
await httpLoader.load({
236+
request: {
237+
responseType: 'arraybuffer',
238+
type: HTTPRequest.MEDIA_SEGMENT_TYPE,
239+
availabilityTimeComplete: true
240+
},
241+
success: callbacks.success,
242+
complete: callbacks.complete,
243+
error: callbacks.error
244+
});
245+
await completePromise;
246+
247+
expect(spy.calledOnce).to.be.true;
248+
});
249+
206250
describe('request timeout selection', function () {
207251
[
208252
{

0 commit comments

Comments
 (0)