bookmark-audio-fragment/app/util/ parse-media-fragment-identifier.js
39 lines
1.7 KiB

  1. // Parses the time range from a media fragment identifier such as '#t=12.5,1:59:45.12'.
  2. // See https://www.w3.org/TR/media-frags/
  3. // Note this only supports npt (normal playing time) format, not smpte or wall clock time.
  4. // Returns an object { start, end }; both numbers are given in seconds (possibly non-integer).
  5. export default function parseMediaFragmentIdentifier(fragmentIdentifier) {
  6. // Strip possible leading hash character '#'.
  7. if (fragmentIdentifier.startsWith('#')) {
  8. fragmentIdentifier = fragmentIdentifier.substring(1);
  9. }
  10. const temporalDimensionRegex = /(?:^|&)(?:t|%74)=([^&]+)(?=&|$)/g;
  11. const temporalDimensionMatches = [...fragmentIdentifier.matchAll(temporalDimensionRegex)];
  12. if (temporalDimensionMatches.length === 0) {
  13. throw new Error('No time dimension defined');
  14. }
  15. // If there are multiple occurrences (e.g. t=1&t=2), take the last one.
  16. const temporalDimensionMatch = temporalDimensionMatches[temporalDimensionMatches.length - 1];
  17. const temporalDimensionValue = decodeURIComponent(temporalDimensionMatch[1]);
  18. const nptRegex = /^(?:npt:)?(?:(?:(\d+):)??(?:(\d+):)?(\d+(?:\.\d*)?))?(?:,(?:(\d+):)??(?:(\d+):)?(\d+(?:\.\d*)?))?$/;
  19. const nptMatch = temporalDimensionValue.match(nptRegex);
  20. if (!nptMatch) {
  21. throw new Error('Unable to parse fragment identifier.');
  22. }
  23. const start = (nptMatch[3] ? Number.parseFloat(nptMatch[3]) : 0)
  24. + (nptMatch[2] ? Number.parseInt(nptMatch[2]) * 60 : 0)
  25. + (nptMatch[1] ? Number.parseInt(nptMatch[1]) * 60 * 60 : 0);
  26. let end;
  27. if (nptMatch[6]) {
  28. end = Number.parseFloat(nptMatch[6])
  29. + (nptMatch[5] ? Number.parseInt(nptMatch[5]) * 60 : 0)
  30. + (nptMatch[4] ? Number.parseInt(nptMatch[4]) * 60 * 60 : 0);
  31. }
  32. return { start, end };
  33. }