Skip to content

Commit

Permalink
feat(dash): Enable Stateful Mode and rsq Corruptions for DASH
Browse files Browse the repository at this point in the history
- Imported STATEFUL and newState utilities for DASH (segment.ts, dashManifestUtils.ts and dashManifestUtils.test.ts) to support stateful mode.
- Ensured rsq corruptions are possible for DASH when the proxy is running in stateful mode, similar to how rsq corruptions are enabled for HLS.

This update allows for the use of relative sequence numbers in DASH manifests, aligning the functionality with HLS. The DASH proxy now properly handles stateful operations and rsq parameters, enabling more flexible stream corruptions.
  • Loading branch information
Kajlid committed Jun 26, 2024
1 parent e3c81b4 commit 7ae3c71
Show file tree
Hide file tree
Showing 93 changed files with 15,168 additions and 7 deletions.
112 changes: 112 additions & 0 deletions .history/src/manifests/handlers/dash/segment_20240613120409.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { ALBEvent, ALBResult } from 'aws-lambda';
import { ServiceError } from '../../../shared/types';
import {
composeALBEvent,
generateErrorResponse,
isValidUrl,
segmentUrlParamString
} from '../../../shared/utils';
import delaySCC from '../../utils/corruptions/delay';
import statusCodeSCC from '../../utils/corruptions/statusCode';
import timeoutSCC from '../../utils/corruptions/timeout';
import throttleSCC from '../../utils/corruptions/throttle';
import path from 'path';
import dashManifestUtils from '../../utils/dashManifestUtils';
import { corruptorConfigUtils } from '../../utils/configs';
import segmentHandler from '../../../segments/handlers/segment';

export default async function dashSegmentHandler(
event: ALBEvent
): Promise<ALBResult> {
/**
* #1 - const originalUrl = req.body.query("url");
* #2 - const originalManifest = await fetch(originalUrl);
* #3 - build proxy version and response with the correct header
*/
const { url } = event.queryStringParameters;

if (!url || !isValidUrl(url)) {
const errorRes: ServiceError = {
status: 400,
message: "Missing a valid 'url' query parameter"
};
return generateErrorResponse(errorRes);
}

try {
const urlSearchParams = new URLSearchParams(event.queryStringParameters);
const pathStem = path.basename(event.path).replace('.mp4', '');
// Get the number part after "segment_"
const [, reqSegmentIndexOrTimeStr, bitrateStr, ...representationIdStrList] =
pathStem.split('_');
const representationIdStr = representationIdStrList.join('_');
// Build correct Source Segment url
// segment templates may contain a width parameter "$Number%0[width]d$", and then we need to zero-pad them to that length

let segmentUrl = url;

if (segmentUrl.includes('$Time$')) {
segmentUrl = segmentUrl.replace('$Time$', reqSegmentIndexOrTimeStr);
} else {
segmentUrl = segmentUrl
.replace(/\$Number%0(\d+)d\$/, (_, width) =>
reqSegmentIndexOrTimeStr.padStart(Number(width), '0')
)
.replace('$Number$', reqSegmentIndexOrTimeStr);
}
const reqSegmentIndexInt = parseInt(reqSegmentIndexOrTimeStr);

// Replace RepresentationID in url if present
if (representationIdStr) {
segmentUrl = segmentUrl.replace(
'$RepresentationID$',
representationIdStr
);
}

if (bitrateStr) {
urlSearchParams.set('bitrate', bitrateStr);
}
// Break down Corruption Objects
// Send source URL with a corruption json (if it is appropriate) to segmentHandler...
const configUtils = corruptorConfigUtils(urlSearchParams);
configUtils
.register(delaySCC)
.register(statusCodeSCC)
.register(timeoutSCC)
.register(throttleSCC);
const [error, allMutations] = configUtils.getAllManifestConfigs(
reqSegmentIndexInt,
true
);
if (error) {
return generateErrorResponse(error);
}
const dashUtils = dashManifestUtils();
const mergedMaps = dashUtils.utils.mergeMap(
reqSegmentIndexInt,
allMutations
);
const segUrl = new URL(segmentUrl);
const cleanSegUrl = segUrl.origin + segUrl.pathname;
let eventParamsString: string;
if (mergedMaps.size < 1) {
eventParamsString = `url=${cleanSegUrl}`;
} else {
eventParamsString = segmentUrlParamString(cleanSegUrl, mergedMaps);
}
const albEvent = await composeALBEvent(
event.httpMethod,
`${event.path}?${eventParamsString}`,
event.headers
);
return await segmentHandler(albEvent);
} catch (err) {
const errorRes: ServiceError = {
status: 500,
message: err.message ? err.message : err
};
//for unexpected errors
return generateErrorResponse(errorRes);
}
}
113 changes: 113 additions & 0 deletions .history/src/manifests/handlers/dash/segment_20240626093448.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { ALBEvent, ALBResult } from 'aws-lambda';
import { ServiceError } from '../../../shared/types';
import {
composeALBEvent,
generateErrorResponse,
isValidUrl,
segmentUrlParamStringSTATEFUL,
newState
} from '../../../shared/utils';
import delaySCC from '../../utils/corruptions/delay';
import statusCodeSCC from '../../utils/corruptions/statusCode';
import timeoutSCC from '../../utils/corruptions/timeout';
import throttleSCC from '../../utils/corruptions/throttle';
import path from 'path';
import dashManifestUtils from '../../utils/dashManifestUtils';
import { corruptorConfigUtils } from '../../utils/configs';
import segmentHandler from '../../../segments/handlers/segment';

export default async function dashSegmentHandler(
event: ALBEvent
): Promise<ALBResult> {
/**
* #1 - const originalUrl = req.body.query("url");
* #2 - const originalManifest = await fetch(originalUrl);
* #3 - build proxy version and response with the correct header
*/
const { url } = event.queryStringParameters;

if (!url || !isValidUrl(url)) {
const errorRes: ServiceError = {
status: 400,
message: "Missing a valid 'url' query parameter"
};
return generateErrorResponse(errorRes);
}

try {
const urlSearchParams = new URLSearchParams(event.queryStringParameters);
const pathStem = path.basename(event.path).replace('.mp4', '');
// Get the number part after "segment_"
const [, reqSegmentIndexOrTimeStr, bitrateStr, ...representationIdStrList] =
pathStem.split('_');
const representationIdStr = representationIdStrList.join('_');
// Build correct Source Segment url
// segment templates may contain a width parameter "$Number%0[width]d$", and then we need to zero-pad them to that length

let segmentUrl = url;

if (segmentUrl.includes('$Time$')) {
segmentUrl = segmentUrl.replace('$Time$', reqSegmentIndexOrTimeStr);
} else {
segmentUrl = segmentUrl
.replace(/\$Number%0(\d+)d\$/, (_, width) =>
reqSegmentIndexOrTimeStr.padStart(Number(width), '0')
)
.replace('$Number$', reqSegmentIndexOrTimeStr);
}
const reqSegmentIndexInt = parseInt(reqSegmentIndexOrTimeStr);

// Replace RepresentationID in url if present
if (representationIdStr) {
segmentUrl = segmentUrl.replace(
'$RepresentationID$',
representationIdStr
);
}

if (bitrateStr) {
urlSearchParams.set('bitrate', bitrateStr);
}
// Break down Corruption Objects
// Send source URL with a corruption json (if it is appropriate) to segmentHandler...
const configUtils = corruptorConfigUtils(urlSearchParams);
configUtils
.register(delaySCC)
.register(statusCodeSCC)
.register(timeoutSCC)
.register(throttleSCC);
const [error, allMutations] = configUtils.getAllManifestConfigs(
reqSegmentIndexInt,
true
);
if (error) {
return generateErrorResponse(error);
}
const dashUtils = dashManifestUtils();
const mergedMaps = dashUtils.utils.mergeMap(
reqSegmentIndexInt,
allMutations
);
const segUrl = new URL(segmentUrl);
const cleanSegUrl = segUrl.origin + segUrl.pathname;
let eventParamsString: string;
if (mergedMaps.size < 1) {
eventParamsString = `url=${cleanSegUrl}`;
} else {
eventParamsString = segmentUrlParamString(cleanSegUrl, mergedMaps);
}
const albEvent = await composeALBEvent(
event.httpMethod,
`${event.path}?${eventParamsString}`,
event.headers
);
return await segmentHandler(albEvent);
} catch (err) {
const errorRes: ServiceError = {
status: 500,
message: err.message ? err.message : err
};
//for unexpected errors
return generateErrorResponse(errorRes);
}
}
114 changes: 114 additions & 0 deletions .history/src/manifests/handlers/dash/segment_20240626093451.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { ALBEvent, ALBResult } from 'aws-lambda';
import { ServiceError } from '../../../shared/types';
import {
composeALBEvent,
generateErrorResponse,
isValidUrl,
segmentUrlParamString,
STATEFUL,
newState
} from '../../../shared/utils';
import delaySCC from '../../utils/corruptions/delay';
import statusCodeSCC from '../../utils/corruptions/statusCode';
import timeoutSCC from '../../utils/corruptions/timeout';
import throttleSCC from '../../utils/corruptions/throttle';
import path from 'path';
import dashManifestUtils from '../../utils/dashManifestUtils';
import { corruptorConfigUtils } from '../../utils/configs';
import segmentHandler from '../../../segments/handlers/segment';

export default async function dashSegmentHandler(
event: ALBEvent
): Promise<ALBResult> {
/**
* #1 - const originalUrl = req.body.query("url");
* #2 - const originalManifest = await fetch(originalUrl);
* #3 - build proxy version and response with the correct header
*/
const { url } = event.queryStringParameters;

if (!url || !isValidUrl(url)) {
const errorRes: ServiceError = {
status: 400,
message: "Missing a valid 'url' query parameter"
};
return generateErrorResponse(errorRes);
}

try {
const urlSearchParams = new URLSearchParams(event.queryStringParameters);
const pathStem = path.basename(event.path).replace('.mp4', '');
// Get the number part after "segment_"
const [, reqSegmentIndexOrTimeStr, bitrateStr, ...representationIdStrList] =
pathStem.split('_');
const representationIdStr = representationIdStrList.join('_');
// Build correct Source Segment url
// segment templates may contain a width parameter "$Number%0[width]d$", and then we need to zero-pad them to that length

let segmentUrl = url;

if (segmentUrl.includes('$Time$')) {
segmentUrl = segmentUrl.replace('$Time$', reqSegmentIndexOrTimeStr);
} else {
segmentUrl = segmentUrl
.replace(/\$Number%0(\d+)d\$/, (_, width) =>
reqSegmentIndexOrTimeStr.padStart(Number(width), '0')
)
.replace('$Number$', reqSegmentIndexOrTimeStr);
}
const reqSegmentIndexInt = parseInt(reqSegmentIndexOrTimeStr);

// Replace RepresentationID in url if present
if (representationIdStr) {
segmentUrl = segmentUrl.replace(
'$RepresentationID$',
representationIdStr
);
}

if (bitrateStr) {
urlSearchParams.set('bitrate', bitrateStr);
}
// Break down Corruption Objects
// Send source URL with a corruption json (if it is appropriate) to segmentHandler...
const configUtils = corruptorConfigUtils(urlSearchParams);
configUtils
.register(delaySCC)
.register(statusCodeSCC)
.register(timeoutSCC)
.register(throttleSCC);
const [error, allMutations] = configUtils.getAllManifestConfigs(
reqSegmentIndexInt,
true
);
if (error) {
return generateErrorResponse(error);
}
const dashUtils = dashManifestUtils();
const mergedMaps = dashUtils.utils.mergeMap(
reqSegmentIndexInt,
allMutations
);
const segUrl = new URL(segmentUrl);
const cleanSegUrl = segUrl.origin + segUrl.pathname;
let eventParamsString: string;
if (mergedMaps.size < 1) {
eventParamsString = `url=${cleanSegUrl}`;
} else {
eventParamsString = segmentUrlParamString(cleanSegUrl, mergedMaps);
}
const albEvent = await composeALBEvent(
event.httpMethod,
`${event.path}?${eventParamsString}`,
event.headers
);
return await segmentHandler(albEvent);
} catch (err) {
const errorRes: ServiceError = {
status: 500,
message: err.message ? err.message : err
};
//for unexpected errors
return generateErrorResponse(errorRes);
}
}
Loading

0 comments on commit 7ae3c71

Please sign in to comment.