hls.test.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. const m3u8Parser = require('m3u8-parser');
  2. const fetch = require('node-fetch');
  3. const url = require('url');
  4. const { test } = require('@jest/globals');
  5. const HLS_SUBDIRECTORY = '/hls/';
  6. const PLAYLIST_NAME = 'stream.m3u8';
  7. const TEST_OWNCAST_INSTANCE = 'http://localhost:8080';
  8. const HLS_FETCH_ITERATIONS = 5;
  9. jest.setTimeout(40000);
  10. async function getPlaylist(urlString) {
  11. const response = await fetch(urlString);
  12. expect(response.status).toBe(200);
  13. const body = await response.text();
  14. var parser = new m3u8Parser.Parser();
  15. parser.push(body);
  16. parser.end();
  17. return parser.manifest;
  18. }
  19. function normalizeUrl(urlString, baseUrl) {
  20. let parsedString = url.parse(urlString);
  21. if (!parsedString.host) {
  22. const testInstanceRoot = url.parse(baseUrl);
  23. parsedString.protocol = testInstanceRoot.protocol;
  24. parsedString.host = testInstanceRoot.host;
  25. const filename = baseUrl.substring(baseUrl.lastIndexOf('/') + 1);
  26. parsedString.pathname =
  27. testInstanceRoot.pathname.replace(filename, '') + urlString;
  28. }
  29. return url.format(parsedString).toString();
  30. }
  31. // Iterate over an array of video segments and make sure they return back
  32. // valid status.
  33. async function validateSegments(segments) {
  34. for (let segment of segments) {
  35. const res = await fetch(segment);
  36. expect(res.status).toBe(200);
  37. }
  38. }
  39. describe('fetch and parse HLS', () => {
  40. const masterPlaylistUrl = `${TEST_OWNCAST_INSTANCE}${HLS_SUBDIRECTORY}${PLAYLIST_NAME}`;
  41. var masterPlaylist;
  42. var mediaPlaylistUrl;
  43. test('fetch master playlist', async () => {
  44. try {
  45. masterPlaylist = await getPlaylist(masterPlaylistUrl);
  46. } catch (e) {
  47. console.error('error fetching and parsing master playlist', e);
  48. }
  49. });
  50. test('verify there is a media playlist', () => {
  51. // Master playlist should have at least one media playlist.
  52. expect(masterPlaylist.playlists.length).toBe(1);
  53. try {
  54. mediaPlaylistUrl = normalizeUrl(
  55. masterPlaylist.playlists[0].uri,
  56. masterPlaylistUrl
  57. );
  58. } catch (e) {
  59. console.error('error fetching and parsing media playlist', e);
  60. }
  61. });
  62. test('verify there are segments', async () => {
  63. let playlist;
  64. try {
  65. playlist = await getPlaylist(mediaPlaylistUrl);
  66. } catch (e) {
  67. console.error('error verifying segments in media playlist', e);
  68. }
  69. const segments = playlist.segments;
  70. expect(segments.length).toBeGreaterThan(0);
  71. });
  72. // Iterate over segments and make sure they change.
  73. // Use the reported duration of the segment to wait to
  74. // fetch another just like a real HLS player would do.
  75. var lastSegmentUrl;
  76. for (let i = 0; i < HLS_FETCH_ITERATIONS; i++) {
  77. test('fetch and monitor media playlist segments ' + i, async () => {
  78. await new Promise((r) => setTimeout(r, 5000));
  79. try {
  80. var playlist = await getPlaylist(mediaPlaylistUrl);
  81. } catch (e) {
  82. console.error('error updating media playlist', mediaPlaylistUrl, e);
  83. }
  84. const segments = playlist.segments;
  85. const segment = segments[segments.length - 1];
  86. expect(segment.uri).not.toBe(lastSegmentUrl);
  87. try {
  88. var segmentUrl = normalizeUrl(segment.uri, mediaPlaylistUrl);
  89. await validateSegments([segmentUrl]);
  90. } catch (e) {
  91. console.error('unable to validate HLS segment', segmentUrl, e);
  92. }
  93. lastSegmentUrl = segment.uri;
  94. });
  95. }
  96. });