variable-substitution.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import type { AttrList } from './attr-list';
  2. import type { LevelDetails } from '../loader/level-details';
  3. import type { ParsedMultivariantPlaylist } from '../loader/m3u8-parser';
  4. import type { VariableMap } from '../types/level';
  5. const VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g;
  6. export function hasVariableReferences(str: string): boolean {
  7. return VARIABLE_REPLACEMENT_REGEX.test(str);
  8. }
  9. export function substituteVariables(
  10. parsed: Pick<
  11. ParsedMultivariantPlaylist | LevelDetails,
  12. 'variableList' | 'hasVariableRefs' | 'playlistParsingError'
  13. >,
  14. value: string,
  15. ): string {
  16. if (parsed.variableList !== null || parsed.hasVariableRefs) {
  17. const variableList = parsed.variableList;
  18. return value.replace(
  19. VARIABLE_REPLACEMENT_REGEX,
  20. (variableReference: string) => {
  21. const variableName = variableReference.substring(
  22. 2,
  23. variableReference.length - 1,
  24. );
  25. const variableValue = variableList?.[variableName];
  26. if (variableValue === undefined) {
  27. parsed.playlistParsingError ||= new Error(
  28. `Missing preceding EXT-X-DEFINE tag for Variable Reference: "${variableName}"`,
  29. );
  30. return variableReference;
  31. }
  32. return variableValue;
  33. },
  34. );
  35. }
  36. return value;
  37. }
  38. export function addVariableDefinition(
  39. parsed: Pick<
  40. ParsedMultivariantPlaylist | LevelDetails,
  41. 'variableList' | 'playlistParsingError'
  42. >,
  43. attr: AttrList,
  44. parentUrl: string,
  45. ) {
  46. let variableList = parsed.variableList;
  47. if (!variableList) {
  48. parsed.variableList = variableList = {};
  49. }
  50. let NAME: string;
  51. let VALUE;
  52. if ('QUERYPARAM' in attr) {
  53. NAME = attr.QUERYPARAM;
  54. try {
  55. const searchParams = new self.URL(parentUrl).searchParams;
  56. if (searchParams.has(NAME)) {
  57. VALUE = searchParams.get(NAME);
  58. } else {
  59. throw new Error(
  60. `"${NAME}" does not match any query parameter in URI: "${parentUrl}"`,
  61. );
  62. }
  63. } catch (error) {
  64. parsed.playlistParsingError ||= new Error(
  65. `EXT-X-DEFINE QUERYPARAM: ${error.message}`,
  66. );
  67. }
  68. } else {
  69. NAME = attr.NAME;
  70. VALUE = attr.VALUE;
  71. }
  72. if (NAME in variableList) {
  73. parsed.playlistParsingError ||= new Error(
  74. `EXT-X-DEFINE duplicate Variable Name declarations: "${NAME}"`,
  75. );
  76. } else {
  77. variableList[NAME] = VALUE || '';
  78. }
  79. }
  80. export function importVariableDefinition(
  81. parsed: Pick<
  82. ParsedMultivariantPlaylist | LevelDetails,
  83. 'variableList' | 'playlistParsingError'
  84. >,
  85. attr: AttrList,
  86. sourceVariableList: VariableMap | null,
  87. ) {
  88. const IMPORT = attr.IMPORT;
  89. if (sourceVariableList && IMPORT in sourceVariableList) {
  90. let variableList = parsed.variableList;
  91. if (!variableList) {
  92. parsed.variableList = variableList = {};
  93. }
  94. variableList[IMPORT] = sourceVariableList[IMPORT];
  95. } else {
  96. parsed.playlistParsingError ||= new Error(
  97. `EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "${IMPORT}"`,
  98. );
  99. }
  100. }