@amazon/vinyl 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/dist/index.cjs +987 -969
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.js +665 -638
  4. package/dist/index.js.map +4 -4
  5. package/dist/test/benchmark.js.map +2 -2
  6. package/dist/test/integ.es5.js +357 -353
  7. package/dist/test/integ.js +2361 -1870
  8. package/dist/test/integ.js.map +4 -4
  9. package/dist/test/jasmine.css +50 -3
  10. package/dist/test/jasmineHtml.es5.js +195 -155
  11. package/dist/test/jasmineHtml.js +2938 -1833
  12. package/dist/test/jasmineHtml.js.map +3 -3
  13. package/dist/types/current/ad/AdControllerImpl.d.ts.map +1 -1
  14. package/dist/types/current/ad/AdControllerImpl.js +4 -1
  15. package/dist/types/current/ad/discoverHlsInterstitials.d.ts.map +1 -1
  16. package/dist/types/current/ad/discoverHlsInterstitials.js +2 -2
  17. package/dist/types/current/drm/DrmControllerImpl.d.ts +2 -0
  18. package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
  19. package/dist/types/current/drm/DrmControllerImpl.js +56 -48
  20. package/dist/types/current/drm/licenseProvider/defaultLicenseProvider.d.ts.map +1 -1
  21. package/dist/types/current/drm/licenseProvider/defaultLicenseProvider.js +2 -2
  22. package/dist/types/current/index.d.ts +3 -6
  23. package/dist/types/current/index.d.ts.map +1 -1
  24. package/dist/types/current/index.js +3 -6
  25. package/dist/types/current/playback/PlaybackControllerImpl.d.ts +15 -0
  26. package/dist/types/current/playback/PlaybackControllerImpl.d.ts.map +1 -1
  27. package/dist/types/current/playback/PlaybackControllerImpl.js +100 -9
  28. package/dist/types/current/playback/ReadonlyPlaybackController.d.ts +27 -1
  29. package/dist/types/current/playback/ReadonlyPlaybackController.d.ts.map +1 -1
  30. package/dist/types/current/playback/ReadonlyPlaybackController.js +16 -0
  31. package/dist/types/current/playback/createStallDetector.d.ts +43 -0
  32. package/dist/types/current/playback/createStallDetector.d.ts.map +1 -0
  33. package/dist/types/current/playback/createStallDetector.js +101 -0
  34. package/dist/types/current/playback/createStallMonitor.d.ts +45 -0
  35. package/dist/types/current/playback/createStallMonitor.d.ts.map +1 -0
  36. package/dist/types/current/playback/createStallMonitor.js +94 -0
  37. package/dist/types/current/streaming/AudioOptions.d.ts +27 -0
  38. package/dist/types/current/streaming/AudioOptions.d.ts.map +1 -1
  39. package/dist/types/current/streaming/AudioOptions.js +5 -1
  40. package/dist/types/current/streaming/ContentStream.d.ts +0 -5
  41. package/dist/types/current/streaming/ContentStream.d.ts.map +1 -1
  42. package/dist/types/current/streaming/ContentStream.js +0 -4
  43. package/dist/types/current/streaming/LoadMetric.d.ts.map +1 -1
  44. package/dist/types/current/streaming/StreamingEventMap.d.ts +1 -5
  45. package/dist/types/current/streaming/StreamingEventMap.d.ts.map +1 -1
  46. package/dist/types/current/streaming/StreamingEventMap.js +0 -1
  47. package/dist/types/current/streaming/buffering/BufferingController.d.ts +0 -11
  48. package/dist/types/current/streaming/buffering/BufferingController.d.ts.map +1 -1
  49. package/dist/types/current/streaming/buffering/BufferingController.js +0 -13
  50. package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.d.ts.map +1 -1
  51. package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.js +13 -4
  52. package/dist/types/current/text/SidecarTextTrackLoader.d.ts.map +1 -1
  53. package/dist/types/current/text/SidecarTextTrackLoader.js +4 -4
  54. package/dist/types/current/text/TextTrack.d.ts.map +1 -1
  55. package/dist/types/current/track/TrackController.d.ts.map +1 -1
  56. package/dist/types/current/track/TrackController.js +38 -32
  57. package/dist/types/current/track/createSegmentDataProvider.d.ts.map +1 -1
  58. package/dist/types/current/track/createSegmentDataProvider.js +6 -3
  59. package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
  60. package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.d.ts.map +1 -1
  61. package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.js +13 -8
  62. package/dist/types/current/track/dash/createDashFactories.d.ts +1 -1
  63. package/dist/types/current/track/dash/timeline/DashRepresentationSegmentProvider.d.ts.map +1 -1
  64. package/dist/types/current/track/dash/timeline/DashRepresentationSegmentProvider.js +71 -3
  65. package/dist/types/current/track/dash/urlDashManifestProvider.js +2 -2
  66. package/dist/types/current/track/dash/util/mpd.d.ts +6 -0
  67. package/dist/types/current/track/dash/util/mpd.d.ts.map +1 -1
  68. package/dist/types/current/track/dash/util/mpd.js +24 -3
  69. package/dist/types/current/track/filters/resourceTypeFilter.d.ts +4 -0
  70. package/dist/types/current/track/filters/resourceTypeFilter.d.ts.map +1 -1
  71. package/dist/types/current/track/filters/resourceTypeFilter.js +4 -0
  72. package/dist/types/current/track/filters/sampleRateFilter.d.ts +21 -7
  73. package/dist/types/current/track/filters/sampleRateFilter.d.ts.map +1 -1
  74. package/dist/types/current/track/filters/sampleRateFilter.js +42 -24
  75. package/dist/types/current/track/hls/createHlsFactories.d.ts +1 -1
  76. package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts.map +1 -1
  77. package/dist/types/current/track/hls/createUrlHlsManifestProvider.js +3 -3
  78. package/dist/types/current/track/mse/MseTrack.d.ts +9 -17
  79. package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
  80. package/dist/types/current/track/mse/MseTrack.js +29 -46
  81. package/dist/types/current/tsconfig.tsbuildinfo +1 -1
  82. package/dist/types/current/util/media/mimeType.d.ts +8 -0
  83. package/dist/types/current/util/media/mimeType.d.ts.map +1 -0
  84. package/dist/types/current/util/media/mimeType.js +13 -0
  85. package/dist/types/current/vinyl/createVinylFactories.d.ts +7 -16
  86. package/dist/types/current/vinyl/createVinylFactories.d.ts.map +1 -1
  87. package/dist/types/current/vinyl/createVinylFactories.js +2 -12
  88. package/dist/types/ts3.7/drm/DrmControllerImpl.d.ts +2 -0
  89. package/dist/types/ts3.7/index.d.ts +3 -6
  90. package/dist/types/ts3.7/playback/PlaybackControllerImpl.d.ts +15 -0
  91. package/dist/types/ts3.7/playback/ReadonlyPlaybackController.d.ts +28 -0
  92. package/dist/types/ts3.7/playback/createStallDetector.d.ts +43 -0
  93. package/dist/types/ts3.7/playback/createStallMonitor.d.ts +45 -0
  94. package/dist/types/ts3.7/streaming/AudioOptions.d.ts +27 -0
  95. package/dist/types/ts3.7/streaming/ContentStream.d.ts +0 -5
  96. package/dist/types/ts3.7/streaming/StreamingEventMap.d.ts +0 -5
  97. package/dist/types/ts3.7/streaming/buffering/BufferingController.d.ts +0 -11
  98. package/dist/types/ts3.7/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
  99. package/dist/types/ts3.7/track/dash/createDashFactories.d.ts +1 -1
  100. package/dist/types/ts3.7/track/dash/util/mpd.d.ts +6 -0
  101. package/dist/types/ts3.7/track/filters/resourceTypeFilter.d.ts +4 -0
  102. package/dist/types/ts3.7/track/filters/sampleRateFilter.d.ts +21 -7
  103. package/dist/types/ts3.7/track/hls/createHlsFactories.d.ts +1 -1
  104. package/dist/types/ts3.7/track/mse/MseTrack.d.ts +9 -17
  105. package/dist/types/ts3.7/util/media/mimeType.d.ts +8 -0
  106. package/dist/types/ts3.7/vinyl/createVinylFactories.d.ts +7 -16
  107. package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +2 -0
  108. package/dist/types/ts4.1/index.d.ts +3 -6
  109. package/dist/types/ts4.1/playback/PlaybackControllerImpl.d.ts +15 -0
  110. package/dist/types/ts4.1/playback/ReadonlyPlaybackController.d.ts +28 -0
  111. package/dist/types/ts4.1/playback/createStallDetector.d.ts +43 -0
  112. package/dist/types/ts4.1/playback/createStallMonitor.d.ts +45 -0
  113. package/dist/types/ts4.1/streaming/AudioOptions.d.ts +27 -0
  114. package/dist/types/ts4.1/streaming/ContentStream.d.ts +0 -5
  115. package/dist/types/ts4.1/streaming/StreamingEventMap.d.ts +0 -5
  116. package/dist/types/ts4.1/streaming/buffering/BufferingController.d.ts +0 -11
  117. package/dist/types/ts4.1/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
  118. package/dist/types/ts4.1/track/dash/createDashFactories.d.ts +1 -1
  119. package/dist/types/ts4.1/track/dash/util/mpd.d.ts +6 -0
  120. package/dist/types/ts4.1/track/filters/resourceTypeFilter.d.ts +4 -0
  121. package/dist/types/ts4.1/track/filters/sampleRateFilter.d.ts +21 -7
  122. package/dist/types/ts4.1/track/hls/createHlsFactories.d.ts +1 -1
  123. package/dist/types/ts4.1/track/mse/MseTrack.d.ts +9 -17
  124. package/dist/types/ts4.1/util/media/mimeType.d.ts +8 -0
  125. package/dist/types/ts4.1/vinyl/createVinylFactories.d.ts +7 -16
  126. package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +2 -0
  127. package/dist/types/ts4.7/index.d.ts +3 -6
  128. package/dist/types/ts4.7/playback/PlaybackControllerImpl.d.ts +15 -0
  129. package/dist/types/ts4.7/playback/ReadonlyPlaybackController.d.ts +28 -0
  130. package/dist/types/ts4.7/playback/createStallDetector.d.ts +43 -0
  131. package/dist/types/ts4.7/playback/createStallMonitor.d.ts +45 -0
  132. package/dist/types/ts4.7/streaming/AudioOptions.d.ts +27 -0
  133. package/dist/types/ts4.7/streaming/ContentStream.d.ts +0 -5
  134. package/dist/types/ts4.7/streaming/StreamingEventMap.d.ts +0 -5
  135. package/dist/types/ts4.7/streaming/buffering/BufferingController.d.ts +0 -11
  136. package/dist/types/ts4.7/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
  137. package/dist/types/ts4.7/track/dash/createDashFactories.d.ts +1 -1
  138. package/dist/types/ts4.7/track/dash/util/mpd.d.ts +6 -0
  139. package/dist/types/ts4.7/track/filters/resourceTypeFilter.d.ts +4 -0
  140. package/dist/types/ts4.7/track/filters/sampleRateFilter.d.ts +21 -7
  141. package/dist/types/ts4.7/track/hls/createHlsFactories.d.ts +1 -1
  142. package/dist/types/ts4.7/track/mse/MseTrack.d.ts +9 -17
  143. package/dist/types/ts4.7/util/media/mimeType.d.ts +8 -0
  144. package/dist/types/ts4.7/vinyl/createVinylFactories.d.ts +7 -16
  145. package/dist/unit/types/current/ad/AdControllerImpl.test.js +27 -1
  146. package/dist/unit/types/current/drm/DrmControllerImpl.test.js +93 -0
  147. package/dist/unit/types/current/index.d.ts +3 -4
  148. package/dist/unit/types/current/index.d.ts.map +1 -1
  149. package/dist/unit/types/current/index.js +3 -4
  150. package/dist/unit/types/current/playback/PlaybackControllerImpl.test.js +165 -0
  151. package/dist/unit/types/current/playback/createStallDetector.test.d.ts +2 -0
  152. package/dist/unit/types/current/playback/createStallDetector.test.d.ts.map +1 -0
  153. package/dist/unit/types/current/playback/createStallDetector.test.js +163 -0
  154. package/dist/unit/types/current/playback/createStallMonitor.test.d.ts +2 -0
  155. package/dist/unit/types/current/playback/createStallMonitor.test.d.ts.map +1 -0
  156. package/dist/unit/types/current/playback/createStallMonitor.test.js +125 -0
  157. package/dist/unit/types/current/streaming/ContentStream.test.js +0 -11
  158. package/dist/unit/types/current/streaming/MseTrack.test.js +47 -74
  159. package/dist/unit/types/current/streaming/abr/QualitySelectorImpl.test.js +1 -1
  160. package/dist/unit/types/current/streaming/buffering/BufferingController.test.js +0 -21
  161. package/dist/unit/types/current/streaming/createDefaultMediaTimelineTransformer.test.js +133 -0
  162. package/dist/unit/types/current/track/TrackController.test.js +97 -1
  163. package/dist/unit/types/current/track/createSegmentDataProvider.test.js +18 -0
  164. package/dist/unit/types/current/track/dash/DashMediaQualityMetadataResolver.test.js +46 -14
  165. package/dist/unit/types/current/track/dash/createSegmentControllerFactory.test.js +1 -0
  166. package/dist/unit/types/current/track/dash/timeline/DashRepresentationSegmentProvider.test.js +114 -0
  167. package/dist/unit/types/current/track/dash/util/mpd.test.js +37 -0
  168. package/dist/unit/types/current/track/filters/resourceTypeFilter.test.js +7 -0
  169. package/dist/unit/types/current/track/filters/sampleRateFilter.test.js +136 -34
  170. package/dist/unit/types/current/track/source/SourceObjectTrack.test.js +1 -1
  171. package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
  172. package/dist/unit/types/current/util/media/mimeType.test.d.ts +2 -0
  173. package/dist/unit/types/current/util/media/mimeType.test.d.ts.map +1 -0
  174. package/dist/unit/types/current/util/media/mimeType.test.js +22 -0
  175. package/dist/unit/types/current/vinyl/createVinylFactories.test.js +2 -36
  176. package/dist/vinylTestUtil/index.js +6 -4
  177. package/dist/vinylTestUtil/index.js.map +2 -2
  178. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts +1 -0
  179. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts.map +1 -1
  180. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.js +1 -0
  181. package/dist/vinylTestUtil/types/current/media/FrequencyAnalyzer.d.ts.map +1 -1
  182. package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.d.ts +0 -1
  183. package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.d.ts.map +1 -1
  184. package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.js +0 -1
  185. package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
  186. package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.d.ts.map +1 -1
  187. package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.js +0 -1
  188. package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
  189. package/dist/vinylTestUtil/types/current/util/playback/eventPromises.js +1 -1
  190. package/dist/vinylTestUtil/types/current/util/playback/expectTrackCanSeek.d.ts.map +1 -1
  191. package/dist/vinylTestUtil/types/current/util/playback/expectTrackCanSeek.js +9 -1
  192. package/dist/vinylTestUtil/types/ts3.7/assets/vinylTestAssets.d.ts +1 -0
  193. package/dist/vinylTestUtil/types/ts3.7/mock/streaming/MockContentStream.d.ts +0 -1
  194. package/dist/vinylTestUtil/types/ts3.7/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
  195. package/dist/vinylTestUtil/types/ts4.1/assets/vinylTestAssets.d.ts +1 -0
  196. package/dist/vinylTestUtil/types/ts4.1/mock/streaming/MockContentStream.d.ts +0 -1
  197. package/dist/vinylTestUtil/types/ts4.1/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
  198. package/dist/vinylTestUtil/types/ts4.7/assets/vinylTestAssets.d.ts +1 -0
  199. package/dist/vinylTestUtil/types/ts4.7/mock/streaming/MockContentStream.d.ts +0 -1
  200. package/dist/vinylTestUtil/types/ts4.7/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
  201. package/docs/USAGE.md +29 -0
  202. package/package.json +11 -10
  203. package/dist/types/current/patch/media/HtmlMediaElementPatch.d.ts +0 -3
  204. package/dist/types/current/patch/media/HtmlMediaElementPatch.d.ts.map +0 -1
  205. package/dist/types/current/patch/media/HtmlMediaElementPatch.js +0 -5
  206. package/dist/types/current/patch/media/MediaElementPatchOptions.d.ts +0 -19
  207. package/dist/types/current/patch/media/MediaElementPatchOptions.d.ts.map +0 -1
  208. package/dist/types/current/patch/media/MediaElementPatchOptions.js +0 -16
  209. package/dist/types/current/patch/media/patchMediaElement.d.ts +0 -9
  210. package/dist/types/current/patch/media/patchMediaElement.d.ts.map +0 -1
  211. package/dist/types/current/patch/media/patchMediaElement.js +0 -15
  212. package/dist/types/current/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
  213. package/dist/types/current/patch/media/patches/createPreventStallsPatch.d.ts.map +0 -1
  214. package/dist/types/current/patch/media/patches/createPreventStallsPatch.js +0 -55
  215. package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
  216. package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts.map +0 -1
  217. package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.js +0 -218
  218. package/dist/types/current/vinyl/VinylPatchOptions.d.ts +0 -10
  219. package/dist/types/current/vinyl/VinylPatchOptions.d.ts.map +0 -1
  220. package/dist/types/current/vinyl/VinylPatchOptions.js +0 -11
  221. package/dist/types/ts3.7/patch/media/HtmlMediaElementPatch.d.ts +0 -3
  222. package/dist/types/ts3.7/patch/media/MediaElementPatchOptions.d.ts +0 -19
  223. package/dist/types/ts3.7/patch/media/patchMediaElement.d.ts +0 -9
  224. package/dist/types/ts3.7/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
  225. package/dist/types/ts3.7/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
  226. package/dist/types/ts3.7/vinyl/VinylPatchOptions.d.ts +0 -10
  227. package/dist/types/ts4.1/patch/media/HtmlMediaElementPatch.d.ts +0 -3
  228. package/dist/types/ts4.1/patch/media/MediaElementPatchOptions.d.ts +0 -19
  229. package/dist/types/ts4.1/patch/media/patchMediaElement.d.ts +0 -9
  230. package/dist/types/ts4.1/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
  231. package/dist/types/ts4.1/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
  232. package/dist/types/ts4.1/vinyl/VinylPatchOptions.d.ts +0 -10
  233. package/dist/types/ts4.7/patch/media/HtmlMediaElementPatch.d.ts +0 -3
  234. package/dist/types/ts4.7/patch/media/MediaElementPatchOptions.d.ts +0 -19
  235. package/dist/types/ts4.7/patch/media/patchMediaElement.d.ts +0 -9
  236. package/dist/types/ts4.7/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
  237. package/dist/types/ts4.7/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
  238. package/dist/types/ts4.7/vinyl/VinylPatchOptions.d.ts +0 -10
  239. package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.d.ts +0 -2
  240. package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.d.ts.map +0 -1
  241. package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.js +0 -17
  242. package/dist/unit/types/current/patch/media/patchMediaElement.test.d.ts +0 -2
  243. package/dist/unit/types/current/patch/media/patchMediaElement.test.d.ts.map +0 -1
  244. package/dist/unit/types/current/patch/media/patchMediaElement.test.js +0 -36
  245. package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.d.ts +0 -2
  246. package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.d.ts.map +0 -1
  247. package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.js +0 -93
  248. package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.d.ts +0 -2
  249. package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.d.ts.map +0 -1
  250. package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.js +0 -423
@@ -422,7 +422,7 @@ describe('getPeriodSortedBandwidths', () => {
422
422
  });
423
423
  });
424
424
  describe('estimatePeakBandwidth', () => {
425
- it('pairs proportionally when video has more qualities than audio', () => {
425
+ it('pairs each tier with the cheapest peer in its bucket', () => {
426
426
  // language=XML
427
427
  const m = parseDashManifest(`<?xml version="1.0" ?>
428
428
  <MPD minBufferTime="PT0S" profiles="" xmlns="urn:mpeg:dash:schema:mpd:2011">
@@ -441,20 +441,51 @@ describe('estimatePeakBandwidth', () => {
441
441
  </MPD>`);
442
442
  const as0 = m.MPD.Period[0].AdaptationSet[0];
443
443
  const as1 = m.MPD.Period[0].AdaptationSet[1];
444
- // video sorted: [4000, 3000, 2000, 1000], audio sorted: [200, 100]
445
- // v1 pos=0/3=0.00 → audio index round(0.00*1)=0 → 200. total=4200
444
+ // video sorted: [4000, 3000, 2000, 1000], audio sorted: [200, 100].
445
+ // Video's two audio buckets: v1,v2 → a1(200); v3,v4 → a2(100).
446
446
  expect(estimatePeakBandwidth(as0.Representation[0], 'video')).toBe(4200);
447
- // v2 pos=1/3=0.33 → audio index round(0.33*1)=0 → 200. total=3200
448
447
  expect(estimatePeakBandwidth(as0.Representation[1], 'video')).toBe(3200);
449
- // v3 pos=2/3=0.67 → audio index round(0.67*1)=1 → 100. total=2100
450
448
  expect(estimatePeakBandwidth(as0.Representation[2], 'video')).toBe(2100);
451
- // v4 pos=3/3=1.00 → audio index round(1.00*1)=1 → 100. total=1100
452
449
  expect(estimatePeakBandwidth(as0.Representation[3], 'video')).toBe(1100);
453
- // a1 pos=0/1=0 → video index round(0*3)=0 → 4000. total=4200
454
- expect(estimatePeakBandwidth(as1.Representation[0], 'audio')).toBe(4200);
455
- // a2 pos=1/1=1 → video index round(1*3)=3 → 1000. total=1100
450
+ // Audio's two video buckets: a1's bucket is v1,v2 so it pairs with the
451
+ // cheapest of those (v2=3000); a2's bucket is v3,v4 (cheapest v4=1000).
452
+ expect(estimatePeakBandwidth(as1.Representation[0], 'audio')).toBe(3200);
456
453
  expect(estimatePeakBandwidth(as1.Representation[1], 'audio')).toBe(1100);
457
454
  });
455
+ it('does not pin a small stream to its lowest tier across a finer peer', () => {
456
+ // 2 audio tiers alongside 10 video tiers: the top audio must stay
457
+ // affordable across the whole top half of video, not only at the very
458
+ // top. (Regression: it was priced with the single top video.)
459
+ // language=XML
460
+ const m = parseDashManifest(`<?xml version="1.0" ?>
461
+ <MPD minBufferTime="PT0S" profiles="" xmlns="urn:mpeg:dash:schema:mpd:2011">
462
+ <Period>
463
+ <AdaptationSet mimeType="video/mp4" codecs="avc1.640015">
464
+ <Representation bandwidth="10000" id="v1"/>
465
+ <Representation bandwidth="9000" id="v2"/>
466
+ <Representation bandwidth="8000" id="v3"/>
467
+ <Representation bandwidth="7000" id="v4"/>
468
+ <Representation bandwidth="6000" id="v5"/>
469
+ <Representation bandwidth="5000" id="v6"/>
470
+ <Representation bandwidth="4000" id="v7"/>
471
+ <Representation bandwidth="3000" id="v8"/>
472
+ <Representation bandwidth="2000" id="v9"/>
473
+ <Representation bandwidth="1000" id="v10"/>
474
+ </AdaptationSet>
475
+ <AdaptationSet mimeType="audio/mp4" codecs="mp4a.40.2">
476
+ <Representation bandwidth="200" id="a1"/>
477
+ <Representation bandwidth="100" id="a2"/>
478
+ </AdaptationSet>
479
+ </Period>
480
+ </MPD>`);
481
+ const audio = m.MPD.Period[0].AdaptationSet[1];
482
+ // Top audio's bucket is video[0..4]; it pairs with the cheapest of those
483
+ // (video[4]=6000), NOT video[0]=10000. So its total is 6200, keeping it
484
+ // affordable for the top half of video tiers.
485
+ expect(estimatePeakBandwidth(audio.Representation[0], 'audio')).toBe(6200);
486
+ // Bottom audio's bucket is video[5..9]; cheapest video[9]=1000 → 1100.
487
+ expect(estimatePeakBandwidth(audio.Representation[1], 'audio')).toBe(1100);
488
+ });
458
489
  it('returns bandwidth as-is for single content type periods', () => {
459
490
  // language=XML
460
491
  const m = parseDashManifest(`<?xml version="1.0" ?>
@@ -482,7 +513,7 @@ describe('estimatePeakBandwidth', () => {
482
513
  </MPD>`);
483
514
  expect(estimatePeakBandwidth(m.MPD.Period[0].AdaptationSet[0].Representation[0], 'text')).toBe(50000);
484
515
  });
485
- it('pairs proportionally with three content types', () => {
516
+ it('pairs with the cheapest peer bucket across three content types', () => {
486
517
  // language=XML
487
518
  const m = parseDashManifest(`<?xml version="1.0" ?>
488
519
  <MPD minBufferTime="PT0S" profiles="" xmlns="urn:mpeg:dash:schema:mpd:2011">
@@ -500,12 +531,13 @@ describe('estimatePeakBandwidth', () => {
500
531
  </AdaptationSet>
501
532
  </Period>
502
533
  </MPD>`);
503
- // v1 pos=0 → audio[0]=200, text[0]=10. total=2210
534
+ // v1 → cheapest audio in its bucket (200) + cheapest text (10). total=2210
504
535
  expect(estimatePeakBandwidth(m.MPD.Period[0].AdaptationSet[0].Representation[0], 'video')).toBe(2210);
505
- // v2 pos=1 → audio[1]=100, text[0]=10. total=1110
536
+ // v2 → audio bucket cheapest (100) + text (10). total=1110
506
537
  expect(estimatePeakBandwidth(m.MPD.Period[0].AdaptationSet[0].Representation[1], 'video')).toBe(1110);
507
- // t1 pos=0 (only 1 text) → video[0]=2000, audio[0]=200. total=2210
508
- expect(estimatePeakBandwidth(m.MPD.Period[0].AdaptationSet[2].Representation[0], 'text')).toBe(2210);
538
+ // t1 is the only text tier, so its bucket spans every peer — it pairs
539
+ // with the cheapest video (1000) and cheapest audio (100). total=1110
540
+ expect(estimatePeakBandwidth(m.MPD.Period[0].AdaptationSet[2].Representation[0], 'text')).toBe(1110);
509
541
  });
510
542
  it('handles single quality per content type', () => {
511
543
  // language=XML
@@ -22,5 +22,6 @@ describe('createSegmentControllerFactory', () => {
22
22
  });
23
23
  expect(controller).toEqual(jasmine.any(Object));
24
24
  expect(controller.options).toEqual(objectContaining({ prefetchActive: 100 }));
25
+ controller.dispose();
25
26
  });
26
27
  });
@@ -277,6 +277,120 @@ describe('createDashRepresentationSegmentProvider', () => {
277
277
  });
278
278
  });
279
279
  });
280
+ describe('when SegmentBase is a WebM stream', () => {
281
+ // Minimal EBML builders for a synthetic WebM init + Cues.
282
+ const vintSize = (n) => n < 0x80 ? [0x80 | n] : [0x40 | (n >> 8), n & 0xff];
283
+ const el = (id, content) => [
284
+ ...id,
285
+ ...vintSize(content.length),
286
+ ...content,
287
+ ];
288
+ const bytesToArrayBuffer = (bytes) => new Uint8Array(bytes).buffer;
289
+ // EBML header (5 bytes) then Segment(Info > TimecodeScale=1_000_000);
290
+ // the Segment data starts at offset 10, so cue positions are + 10.
291
+ const initBuffer = bytesToArrayBuffer([
292
+ ...el([0x1a, 0x45, 0xdf, 0xa3], []),
293
+ ...el([0x18, 0x53, 0x80, 0x67], el([0x15, 0x49, 0xa9, 0x66], el([0x2a, 0xd7, 0xb1], [0x0f, 0x42, 0x40]))),
294
+ ]);
295
+ const cuePoint = (time, pos) => el([0xbb], [
296
+ ...el([0xb3], time),
297
+ ...el([0xb7], [...el([0xf7], [0x01]), ...el([0xf1], pos)]),
298
+ ]);
299
+ // Cue 0 @ t=0 → cluster pos 100; cue 1 @ t=2000ms → cluster pos 200.
300
+ const cuesBuffer = bytesToArrayBuffer(el([0x1c, 0x53, 0xbb, 0x6b], [
301
+ ...cuePoint([0x00], [0x00, 0x64]),
302
+ ...cuePoint([0x07, 0xd0], [0x00, 0xc8]),
303
+ ]));
304
+ beforeEach(() => {
305
+ // WebM Cues don't carry the last segment's end, so a period end is
306
+ // required (VOD defines one).
307
+ manifest.MPD.mediaPresentationDuration = 60;
308
+ representation.BaseURL = [
309
+ { _content: 'https://example.com/v.webm' },
310
+ ];
311
+ representation.mimeType = 'video/webm';
312
+ representation.codecs = 'vp9';
313
+ representation.SegmentBase = {
314
+ indexRangeExact: false,
315
+ indexRange: [100, 199],
316
+ timescale: 1000000,
317
+ Initialization: { range: [0, 99] },
318
+ };
319
+ // The provider fetches the init and Cues ranges in parallel; return
320
+ // the matching buffer per Range header.
321
+ mockRequesterRef.value.request.and.callFake(((_input, init) => {
322
+ const range = init?.headers?.['Range'];
323
+ const body = range === 'bytes=0-99'
324
+ ? initBuffer
325
+ : range === 'bytes=100-199'
326
+ ? cuesBuffer
327
+ : new ArrayBuffer(0);
328
+ return Promise.resolve(new Response(body));
329
+ }));
330
+ });
331
+ it('creates a timeline from the WebM Cues', async () => {
332
+ const timeline = createTimeline();
333
+ // Prime the timeline (fetch init + Cues), then assert the segments.
334
+ await timeline.getSegment(0);
335
+ clearRequestCalls();
336
+ const info = await getSegmentRequestData(timeline);
337
+ expect(info).toEqual({
338
+ init: {
339
+ qualityId: any(String),
340
+ decoderId: any(String),
341
+ mimeType: 'video/webm; codecs="vp9"',
342
+ href: 'https://example.com/v.webm',
343
+ serviceId: 'example.com',
344
+ range: 'bytes=0-99',
345
+ },
346
+ segments: [
347
+ {
348
+ startTime: 0,
349
+ endTime: 2,
350
+ timestampOffset: 0,
351
+ href: 'https://example.com/v.webm',
352
+ serviceId: 'example.com',
353
+ // segmentDataStart(10) + cue positions 100..200.
354
+ range: 'bytes=110-209',
355
+ },
356
+ {
357
+ startTime: 2,
358
+ endTime: 60,
359
+ timestampOffset: 2,
360
+ href: 'https://example.com/v.webm',
361
+ serviceId: 'example.com',
362
+ // Last cluster runs open-ended to end of file.
363
+ range: 'bytes=210-',
364
+ },
365
+ ],
366
+ });
367
+ });
368
+ it('indexes WebM without an explicit SegmentBase timescale', async () => {
369
+ delete representation.SegmentBase
370
+ .timescale;
371
+ const timeline = createTimeline();
372
+ const segment = await timeline.getSegment(0);
373
+ expect(segment).not.toBeNull();
374
+ // Cue 0 at ticks 0 → 0s (TimecodeScale-derived ticks-per-second).
375
+ expect(segment.startTime).toBe(0);
376
+ });
377
+ it('throws when the period has no determinable end', () => {
378
+ delete manifest.MPD
379
+ .mediaPresentationDuration;
380
+ const timeline = createTimeline();
381
+ return expectAsync(timeline.getSegment(0)).toBeRejectedWithError(/mediaPresentationDuration or Period\.duration required/);
382
+ });
383
+ it('throws when the Cues contain no cue points', () => {
384
+ // A Cues element whose only CuePoint lacks a cluster position.
385
+ const emptyCues = bytesToArrayBuffer(el([0x1c, 0x53, 0xbb, 0x6b], el([0xbb], el([0xb3], [0x00]))));
386
+ mockRequesterRef.value.request.and.callFake(((_input, init) => {
387
+ const range = init?.headers?.['Range'];
388
+ return Promise.resolve(new Response(range === 'bytes=0-99' ? initBuffer : emptyCues));
389
+ }));
390
+ const timeline = createTimeline();
391
+ return expectAsync(timeline.getSegment(0)).toBeRejectedWithError(/Cues contained no cue points/);
392
+ });
393
+ });
280
394
  describe('when SegmentList is defined', () => {
281
395
  let segmentList;
282
396
  beforeEach(() => {
@@ -146,6 +146,31 @@ describe('mpd utils', () => {
146
146
  expect(calculatePeriodStart(manifest.MPD.Period[0])).toEqual(0);
147
147
  });
148
148
  });
149
+ describe('when period.start is absent but periods have durations', () => {
150
+ it('derives the start from the preceding period durations', () => {
151
+ // language=XML
152
+ const manifest = parseDashManifest(`<?xml version="1.0" ?>
153
+ <MPD minBufferTime="PT0.0S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011">
154
+ <Period duration="888.053"/>
155
+ <Period duration="888.053"/>
156
+ <Period duration="888.053"/>
157
+ </MPD>`);
158
+ expect(manifest.MPD.Period.map(calculatePeriodStart)).toEqual([
159
+ 0, 888.053, 1776.106,
160
+ ]);
161
+ });
162
+ it('inherits the previous start when the previous period has no duration', () => {
163
+ // language=XML
164
+ const manifest = parseDashManifest(`<?xml version="1.0" ?>
165
+ <MPD minBufferTime="PT0.0S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011">
166
+ <Period/>
167
+ <Period/>
168
+ </MPD>`);
169
+ // The first period has neither start nor duration, so the
170
+ // second period cannot advance and inherits start 0.
171
+ expect(calculatePeriodStart(manifest.MPD.Period[1])).toEqual(0);
172
+ });
173
+ });
149
174
  });
150
175
  describe('getPeriodAtTime', () => {
151
176
  it('returns the period spanning the given presentation time', () => {
@@ -331,6 +356,18 @@ describe('mpd utils', () => {
331
356
  expect(calculateDuration(manifest)).toBe(50);
332
357
  });
333
358
  });
359
+ describe('when only per-period durations are present', () => {
360
+ it('sums the period durations across the presentation', () => {
361
+ // language=XML
362
+ const manifest = parseDashManifest(`<?xml version="1.0" ?>
363
+ <MPD minBufferTime="PT0.0S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011">
364
+ <Period duration="888.053"/>
365
+ <Period duration="888.053"/>
366
+ <Period duration="888.053"/>
367
+ </MPD>`);
368
+ expect(calculateDuration(manifest)).toBeCloseTo(2664.159, 3);
369
+ });
370
+ });
334
371
  describe('when MPD type is dynamic', () => {
335
372
  it('returns Infinity for live presentations', () => {
336
373
  // language=XML
@@ -22,6 +22,13 @@ describe('canPlayMimeType', () => {
22
22
  expect(canPlayMimeType({ capabilities }, metadata)).toBe(true);
23
23
  expect(capabilities.canPlayTypeMse).toHaveBeenCalledWith('audio/mp4; codecs="opus"');
24
24
  });
25
+ it('supports WebM when the browser reports support', () => {
26
+ capabilities.canPlayTypeMse.and.returnValue(true);
27
+ const metadata = createEmptyMediaQualityMetadata();
28
+ metadata.mimeType = 'video/webm; codecs="vp9"';
29
+ expect(canPlayMimeType({ capabilities }, metadata)).toBe(true);
30
+ expect(capabilities.canPlayTypeMse).toHaveBeenCalledWith('video/webm; codecs="vp9"');
31
+ });
25
32
  it('returns false for null mime type', () => {
26
33
  capabilities.canPlayTypeMse.and.returnValue(true);
27
34
  const metadata = createEmptyMediaQualityMetadata();
@@ -2,56 +2,61 @@
2
2
  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { supportsAudioSamplingRate, createEmptyMediaQualityMetadata, throwSamplingRatesUnsupported, } from '@amazon/vinyl';
5
+ import { withinAudioSampleRateRange, createEmptyMediaQualityMetadata, throwSamplingRatesUnsupported, } from '@amazon/vinyl';
6
6
  import { MockCapabilities } from '@amazon/vinyl/vinylTestUtil';
7
7
  import { setUserAgent } from '@amazon/vinyl-util';
8
- describe('supportsAudioSamplingRate', () => {
8
+ describe('withinAudioSampleRateRange', () => {
9
9
  let capabilities;
10
10
  beforeEach(() => {
11
11
  capabilities = new MockCapabilities();
12
12
  capabilities.sampleRate = 48_000;
13
13
  });
14
+ // The filter only gates audio content types, so gated fixtures must declare
15
+ // contentType 'audio' (the empty metadata defaults it to null).
16
+ function audioQuality(rate) {
17
+ const quality = createEmptyMediaQualityMetadata();
18
+ quality.contentType = 'audio';
19
+ quality.audioSamplingRate = rate;
20
+ return quality;
21
+ }
14
22
  describe('when browser is Firefox', () => {
15
23
  beforeEach(() => {
16
24
  setUserAgent('Firefox');
17
25
  });
18
26
  it('returns false when sample rate is above supported', () => {
19
- const metadata = createEmptyMediaQualityMetadata();
20
- metadata.audioSamplingRate = [96_000];
21
- expect(supportsAudioSamplingRate({ capabilities }, metadata, 0, [
27
+ const metadata = audioQuality([96_000]);
28
+ expect(withinAudioSampleRateRange({ capabilities }, metadata, 0, [
22
29
  metadata,
23
30
  ])).toBe(false);
24
31
  });
25
32
  it('returns false when sample rate is above 48kHz even if capabilities allows it', () => {
26
33
  capabilities.sampleRate = 96_000;
27
- const metadata = createEmptyMediaQualityMetadata();
28
- metadata.audioSamplingRate = [96_000];
29
- expect(supportsAudioSamplingRate({ capabilities }, metadata, 0, [
34
+ const metadata = audioQuality([96_000]);
35
+ expect(withinAudioSampleRateRange({ capabilities }, metadata, 0, [
30
36
  metadata,
31
37
  ])).toBe(false);
32
38
  });
33
39
  it('returns false even when all rates are above 48kHz (no fallback to lowest)', () => {
34
- const low = createEmptyMediaQualityMetadata();
35
- low.audioSamplingRate = [88_200];
36
- const high = createEmptyMediaQualityMetadata();
37
- high.audioSamplingRate = [96_000];
38
- expect(supportsAudioSamplingRate({ capabilities }, low, 0, [low, high])).toBe(false);
39
- expect(supportsAudioSamplingRate({ capabilities }, high, 1, [
40
+ const low = audioQuality([88_200]);
41
+ const high = audioQuality([96_000]);
42
+ expect(withinAudioSampleRateRange({ capabilities }, low, 0, [
43
+ low,
44
+ high,
45
+ ])).toBe(false);
46
+ expect(withinAudioSampleRateRange({ capabilities }, high, 1, [
40
47
  low,
41
48
  high,
42
49
  ])).toBe(false);
43
50
  });
44
51
  it('returns true when sample rate is at or below supported', () => {
45
- const metadata = createEmptyMediaQualityMetadata();
46
- metadata.audioSamplingRate = [48_000];
47
- expect(supportsAudioSamplingRate({ capabilities }, metadata, 0, [
52
+ const metadata = audioQuality([48_000]);
53
+ expect(withinAudioSampleRateRange({ capabilities }, metadata, 0, [
48
54
  metadata,
49
55
  ])).toBe(true);
50
56
  });
51
57
  it('returns true when sample rate is not set', () => {
52
- const metadata = createEmptyMediaQualityMetadata();
53
- metadata.audioSamplingRate = null;
54
- expect(supportsAudioSamplingRate({ capabilities }, metadata, 0, [
58
+ const metadata = audioQuality(null);
59
+ expect(withinAudioSampleRateRange({ capabilities }, metadata, 0, [
55
60
  metadata,
56
61
  ])).toBe(true);
57
62
  });
@@ -61,33 +66,82 @@ describe('supportsAudioSamplingRate', () => {
61
66
  setUserAgent('Chrome');
62
67
  });
63
68
  it('returns true when sample rate is at or below supported', () => {
64
- const metadata = createEmptyMediaQualityMetadata();
65
- metadata.audioSamplingRate = [48_000];
66
- expect(supportsAudioSamplingRate({ capabilities }, metadata, 0, [
69
+ const metadata = audioQuality([48_000]);
70
+ expect(withinAudioSampleRateRange({ capabilities }, metadata, 0, [
71
+ metadata,
72
+ ])).toBe(true);
73
+ });
74
+ it('keeps everything when there is no platform max to gauge support', () => {
75
+ capabilities.sampleRate = null;
76
+ const metadata = audioQuality([96_000]);
77
+ expect(withinAudioSampleRateRange({ capabilities }, metadata, 0, [
67
78
  metadata,
68
79
  ])).toBe(true);
69
80
  });
70
81
  it('returns false when sample rate is above supported and others are below', () => {
71
- const low = createEmptyMediaQualityMetadata();
72
- low.audioSamplingRate = [44_100];
73
- const high = createEmptyMediaQualityMetadata();
74
- high.audioSamplingRate = [96_000];
75
- expect(supportsAudioSamplingRate({ capabilities }, high, 1, [
82
+ const low = audioQuality([44_100]);
83
+ const high = audioQuality([96_000]);
84
+ expect(withinAudioSampleRateRange({ capabilities }, high, 1, [
76
85
  low,
77
86
  high,
78
87
  ])).toBe(false);
79
88
  });
80
89
  it('keeps lowest sample rate when all are above supported', () => {
81
- const low = createEmptyMediaQualityMetadata();
82
- low.audioSamplingRate = [88_200];
83
- const high = createEmptyMediaQualityMetadata();
84
- high.audioSamplingRate = [96_000];
85
- expect(supportsAudioSamplingRate({ capabilities }, low, 0, [low, high])).toBe(true);
86
- expect(supportsAudioSamplingRate({ capabilities }, high, 1, [
90
+ const low = audioQuality([88_200]);
91
+ const high = audioQuality([96_000]);
92
+ expect(withinAudioSampleRateRange({ capabilities }, low, 0, [
93
+ low,
94
+ high,
95
+ ])).toBe(true);
96
+ expect(withinAudioSampleRateRange({ capabilities }, high, 1, [
87
97
  low,
88
98
  high,
89
99
  ])).toBe(false);
90
100
  });
101
+ it('an explicit maxSampleRate takes precedence — raising it keeps a higher rate', () => {
102
+ // AudioContext reports 48kHz, which would drop the 96kHz rendition.
103
+ capabilities.sampleRate = 48_000;
104
+ const low = audioQuality([48_000]);
105
+ const high = audioQuality([96_000]);
106
+ // Without an override, 96kHz is dropped (a 48kHz alternative exists).
107
+ expect(withinAudioSampleRateRange({ capabilities }, high, 1, [
108
+ low,
109
+ high,
110
+ ])).toBe(false);
111
+ // With maxSampleRate = 96kHz, it is kept.
112
+ expect(withinAudioSampleRateRange({ capabilities, maxSampleRate: 96_000 }, high, 1, [low, high])).toBe(true);
113
+ });
114
+ it('an explicit maxSampleRate takes precedence — lowering it drops a rate the platform allows', () => {
115
+ // AudioContext reports 192kHz, which would keep the 96kHz rendition.
116
+ capabilities.sampleRate = 192_000;
117
+ const low = audioQuality([48_000]);
118
+ const high = audioQuality([96_000]);
119
+ expect(withinAudioSampleRateRange({ capabilities }, high, 1, [
120
+ low,
121
+ high,
122
+ ])).toBe(true);
123
+ // Capping at 48kHz drops the 96kHz rendition and keeps the 48kHz one.
124
+ expect(withinAudioSampleRateRange({ capabilities, maxSampleRate: 48_000 }, high, 1, [low, high])).toBe(false);
125
+ expect(withinAudioSampleRateRange({ capabilities, maxSampleRate: 48_000 }, low, 0, [low, high])).toBe(true);
126
+ });
127
+ it('ignores non-audio qualities and keeps audio above the device rate', () => {
128
+ // Output device runs at 44.1kHz while the audio is standard 48kHz.
129
+ capabilities.sampleRate = 44_100;
130
+ const audio = audioQuality([48_000]);
131
+ // A co-present video quality is never gated and must not influence
132
+ // the audio keep-lowest fallback.
133
+ const video = createEmptyMediaQualityMetadata();
134
+ video.contentType = 'video';
135
+ video.audioSamplingRate = null;
136
+ expect(withinAudioSampleRateRange({ capabilities }, audio, 0, [
137
+ audio,
138
+ video,
139
+ ])).toBe(true);
140
+ expect(withinAudioSampleRateRange({ capabilities }, video, 1, [
141
+ audio,
142
+ video,
143
+ ])).toBe(true);
144
+ });
91
145
  });
92
146
  it('throws correct error', () => {
93
147
  try {
@@ -100,3 +154,51 @@ describe('supportsAudioSamplingRate', () => {
100
154
  }
101
155
  });
102
156
  });
157
+ describe('withinAudioSampleRateRange minSampleRate floor', () => {
158
+ let capabilities;
159
+ beforeEach(() => {
160
+ capabilities = new MockCapabilities();
161
+ // High enough that the upper bound never interferes with these tests.
162
+ capabilities.sampleRate = 192_000;
163
+ });
164
+ function audioQuality(rate) {
165
+ const quality = createEmptyMediaQualityMetadata();
166
+ quality.contentType = 'audio';
167
+ quality.audioSamplingRate = rate;
168
+ return quality;
169
+ }
170
+ // minSampleRate is passed via the options arg alongside capabilities.
171
+ function withinMin(metadata, index, array) {
172
+ return withinAudioSampleRateRange({ capabilities, minSampleRate: 48_000 }, metadata, index, array);
173
+ }
174
+ it('passes through non-audio qualities', () => {
175
+ const video = createEmptyMediaQualityMetadata();
176
+ video.contentType = 'video';
177
+ expect(withinMin(video, 0, [video])).toBe(true);
178
+ });
179
+ it('keeps qualities with no sampling rate', () => {
180
+ const quality = audioQuality(null);
181
+ expect(withinMin(quality, 0, [quality])).toBe(true);
182
+ });
183
+ it('drops rates below the floor when a rate at or above it exists', () => {
184
+ const low = audioQuality([24_000]);
185
+ const high = audioQuality([48_000]);
186
+ expect(withinMin(high, 1, [low, high])).toBe(true);
187
+ expect(withinMin(low, 0, [low, high])).toBe(false);
188
+ });
189
+ it('keeps the highest when every rate is below the floor', () => {
190
+ const low = audioQuality([16_000]);
191
+ const mid = audioQuality([24_000]);
192
+ expect(withinMin(mid, 1, [low, mid])).toBe(true);
193
+ expect(withinMin(low, 0, [low, mid])).toBe(false);
194
+ });
195
+ it('ignores non-audio qualities in the floor fallback', () => {
196
+ const audio = audioQuality([24_000]); // below the floor
197
+ const video = createEmptyMediaQualityMetadata();
198
+ video.contentType = 'video';
199
+ video.audioSamplingRate = null;
200
+ // Below the 48kHz floor but the only audio → kept; video is ignored.
201
+ expect(withinMin(audio, 0, [audio, video])).toBe(true);
202
+ expect(withinMin(video, 1, [audio, video])).toBe(true);
203
+ });
204
+ });
@@ -66,7 +66,7 @@ describe('SourceObjectTrack', () => {
66
66
  });
67
67
  });
68
68
  });
69
- describe('createSourceTrackFactory', () => {
69
+ describe('createSourceTrackFactory (object)', () => {
70
70
  let trackFactory;
71
71
  beforeEach(() => {
72
72
  trackFactory = createSourceObjectTrackFactory({