Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

removeLevel API change #6265

Closed
PavelFomin90 opened this issue Mar 6, 2024 · 8 comments · Fixed by #6267
Closed

removeLevel API change #6265

PavelFomin90 opened this issue Mar 6, 2024 · 8 comments · Fixed by #6267

Comments

@PavelFomin90
Copy link
Contributor

What do you want to do with Hls.js?

In 1.5.0 hls.removeLevel support of deleting level by url id was removed.
We using it in our player. Can I return this support in hls or it's not necessary in your vision?

What have you tried so far?

No response

@PavelFomin90 PavelFomin90 added Needs Triage If there is a suspected stream issue, apply this label to triage if it is something we should fix. Question labels Mar 6, 2024
@robwalch
Copy link
Collaborator

robwalch commented Mar 6, 2024

Hi @PavelFomin90,

v1.5 no longer groups redundant HLS variants (with the same attributes but different URLs) into the same HLS.js Level with level.url[] indexes. Instead, they are assigned a content-steering pathway and managed by the content-steering controller. Fallback behavior is then performed by switching pathways rather than setting level ids. More information about this change can be found in #5970.

Can you provide a specific example of when you remove a redundant level (and why)?

@PavelFomin90
Copy link
Contributor Author

PavelFomin90 commented Mar 6, 2024

We have a custom error handling logic.
There was an issue with loading 404 errors (#5647), so that we create a handler for this case, but it broke build-in handling of fallback urls for levels.

We wrote the code which retrying level loading in case 404 or other error code severals time with delay, after that it remove url and go to fallback.

      const removeDamagedStream = (levelIndex: number, currentUrl?: string) => {
        const levels = newHls?.levels || [];
        const currentDetails = levels[levelIndex]?.details;
        const url = currentUrl || currentDetails?.url || levels[levelIndex].uri;
        const urls = levels[levelIndex].url ?? [];
        for (let i = 0; i < urls.length; i++) {
          if (urls[i] === url) {
            for (let j = 0; j < levels.length; j++) {
              if (levels[j]?.url && levels[j].url.length > i) {
                newHls?.removeLevel(j, i);
              }
            }
          }
        }
      };
      if (data.type === Hls.ErrorTypes.NETWORK_ERROR && data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR) {
        const currentLevel = data.context?.level && data.context?.level >= 0 ? data.context?.level : 0;
        if (hls?.levels[currentLevel].url.length === 1 && retryConfig && typeof retryConfig.maxNumRetry === 'number') {
          newHls?.stopLoad();
          const timeoutCb = () => {
            newHls?.startLoad();
            typeof retryConfig.maxNumRetry === 'number' && retryConfig.maxNumRetry--;
          };
          retryConfig.maxNumRetry > 0 && setTimeout(timeoutCb, retryConfig?.retryDelayMs);
        } else {
          removeDamagedStream(currentLevel);
        }
      }

@PavelFomin90
Copy link
Contributor Author

I'll search for content-steering and rewrite our errorHandling

@robwalch
Copy link
Collaborator

robwalch commented Mar 6, 2024

The error handling path runs through retries until no retries or alternates are available before setting error handling action and flags:

case ErrorDetails.LEVEL_LOAD_ERROR:
case ErrorDetails.LEVEL_LOAD_TIMEOUT:
if (typeof context?.level === 'number') {
data.errorAction = this.getPlaylistRetryOrSwitchAction(

private getPlaylistRetryOrSwitchAction(
data: ErrorData,
levelIndex: number | null | undefined,
): IErrorAction {
const hls = this.hls;
const retryConfig = getRetryConfig(hls.config.playlistLoadPolicy, data);
const retryCount = this.playlistError++;
const retry = shouldRetry(
retryConfig,
retryCount,
isTimeoutError(data),
data.response,
);
if (retry) {
return {
action: NetworkErrorAction.RetryRequest,
flags: ErrorActionFlags.None,
retryConfig,
retryCount,
};
}
const errorAction = this.getLevelSwitchAction(data, levelIndex);

private getLevelSwitchAction(

// No levels to switch / Manual level selection / Level not found
// Resolve with Pathway switch, Redundant fail-over, or stay on lowest Level
return {
action: NetworkErrorAction.SendAlternateToPenaltyBox,
flags: ErrorActionFlags.MoveAllAlternatesMatchingHost,

These flags signal to content-steering that a pathway change is appropriate:

if (
errorAction?.action === NetworkErrorAction.SendAlternateToPenaltyBox &&
errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost
) {
const levels = this.levels;
let pathwayPriority = this.pathwayPriority;
let errorPathway = this.pathwayId;
if (data.context) {
const { groupId, pathwayId, type } = data.context;
if (groupId && levels) {
errorPathway = this.getPathwayForGroupId(groupId, type, errorPathway);
} else if (pathwayId) {
errorPathway = pathwayId;
}
}
if (!(errorPathway in this.penalizedPathways)) {
this.penalizedPathways[errorPathway] = performance.now();
}
if (!pathwayPriority && levels) {
// If PATHWAY-PRIORITY was not provided, list pathways for error handling
pathwayPriority = levels.reduce((pathways, level) => {
if (pathways.indexOf(level.pathwayId) === -1) {
pathways.push(level.pathwayId);
}
return pathways;
}, [] as string[]);
}
if (pathwayPriority && pathwayPriority.length > 1) {
this.updatePathwayPriority(pathwayPriority);

@robwalch
Copy link
Collaborator

robwalch commented Mar 6, 2024

There is no public API to update Pathway priority - it is only performed on error or HLS Content Steering Manifest change.

If the built-in error handling is not working for you please describe how. Some of the features you may be interested in contributing:

  • pubic API for changing Pathway priority
    • AND/OR public API for Content Steering commands (client-side manifest updates)
  • improve redundant level Pathway generation to isolate URL host (currently Pathways ".", "..", and so on, are assigned by the order of STREAM-INF tags.

@robwalch robwalch linked a pull request Mar 8, 2024 that will close this issue
1 task
@PavelFomin90
Copy link
Contributor Author

Thank you for answer.
I'll try to rewrite our handling in new way and let you know

@robwalch
Copy link
Collaborator

robwalch commented Mar 12, 2024

I'll try to rewrite our handling in new way and let you know

If you remove all levels with Pathway ".", I would expect HLS.js to change the Pathway to "..", and then fire LEVELS_UPDATED with levels set to have all the redundant levels in Pathway "..". However, this has not been tested. It likely will not work without some changes to the code base. Let me know if this is something you would like.

The only way for you know from the player API that new Pathways were assigned would be to inspect the parsed levels in MANIFEST_LOADED and looking for newly assigned levels[index].attrs['PATHWAY-ID']:

const pathwayCount = (generatePathwaySet[levelKey] += 1);
levelParsed.attrs['PATHWAY-ID'] = new Array(pathwayCount + 1).join('.');

@robwalch robwalch added Missing Feature and removed Needs Triage If there is a suspected stream issue, apply this label to triage if it is something we should fix. labels Mar 12, 2024
@PavelFomin90
Copy link
Contributor Author

I rewrite logic with custom shoudRetry. In a first approximation it's working fine, but need more testing by our QA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants