bookmark-audio-fragment/app/util/ media-fragment-identifier.js
51 lines
2.2 KiB

  1. // Functions for parsing and creating a media fragment identifiers.
  2. // A media fragment is used to specify a time range of an audio/video track, e.g. #t=12,18.5.
  3. // See https://www.w3.org/TR/media-frags/
  4. // Creates a media fragment identifier for a given time range (in seconds).
  5. export function createMediaFragmentIdentifier({ start, end }) {
  6. // Always output the time in seconds, as it is more widely supported than h:m:s syntax.
  7. const maybeStart = start !== undefined ? start : '';
  8. const maybeEnd = end !== undefined ? `,${end}` : '';
  9. const fragmentIdentifier = `t=${maybeStart}${maybeEnd}`;
  10. return fragmentIdentifier;
  11. }
  12. // Parses the time range from a media fragment identifier.
  13. // Returns an object { start, end }; both numbers are given in seconds (possibly non-integer).
  14. // Note this only supports the usual npt (normal playing time) format, not smpte or wall clock time.
  15. export function parseMediaFragmentIdentifier(fragmentIdentifier) {
  16. // Strip possible leading hash character '#'.
  17. if (fragmentIdentifier.startsWith('#')) {
  18. fragmentIdentifier = fragmentIdentifier.substring(1);
  19. }
  20. const temporalDimensionRegex = /(?:^|&)(?:t|%74)=([^&]+)(?=&|$)/g;
  21. const temporalDimensionMatches = [...fragmentIdentifier.matchAll(temporalDimensionRegex)];
  22. if (temporalDimensionMatches.length === 0) {
  23. throw new Error('No time dimension defined');
  24. }
  25. // If there are multiple occurrences (e.g. t=1&t=2), take the last one.
  26. const temporalDimensionMatch = temporalDimensionMatches[temporalDimensionMatches.length - 1];
  27. const temporalDimensionValue = decodeURIComponent(temporalDimensionMatch[1]);
  28. const nptRegex = /^(?:npt:)?(?:(?:(\d+):)??(?:(\d+):)?(\d+(?:\.\d*)?))?(?:,(?:(\d+):)??(?:(\d+):)?(\d+(?:\.\d*)?))?$/;
  29. const nptMatch = temporalDimensionValue.match(nptRegex);
  30. if (!nptMatch) {
  31. throw new Error('Unable to parse fragment identifier.');
  32. }
  33. const start = (nptMatch[3] ? Number.parseFloat(nptMatch[3]) : 0)
  34. + (nptMatch[2] ? Number.parseInt(nptMatch[2]) * 60 : 0)
  35. + (nptMatch[1] ? Number.parseInt(nptMatch[1]) * 60 * 60 : 0);
  36. let end;
  37. if (nptMatch[6]) {
  38. end = Number.parseFloat(nptMatch[6])
  39. + (nptMatch[5] ? Number.parseInt(nptMatch[5]) * 60 : 0)
  40. + (nptMatch[4] ? Number.parseInt(nptMatch[4]) * 60 * 60 : 0);
  41. }
  42. return { start, end };
  43. }