@amazon/vinyl 3.0.1 → 3.2.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 (268) hide show
  1. package/dist/index.cjs +1103 -1042
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.js +810 -739
  4. package/dist/index.js.map +4 -4
  5. package/dist/test/benchmark.js.map +2 -2
  6. package/dist/test/integ.es5.js +394 -388
  7. package/dist/test/integ.js +2626 -2032
  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 +19 -1
  18. package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
  19. package/dist/types/current/drm/DrmControllerImpl.js +101 -58
  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 +15 -1
  44. package/dist/types/current/streaming/LoadMetric.d.ts.map +1 -1
  45. package/dist/types/current/streaming/StreamingEventMap.d.ts +1 -5
  46. package/dist/types/current/streaming/StreamingEventMap.d.ts.map +1 -1
  47. package/dist/types/current/streaming/StreamingEventMap.js +0 -1
  48. package/dist/types/current/streaming/buffering/BufferingController.d.ts +0 -11
  49. package/dist/types/current/streaming/buffering/BufferingController.d.ts.map +1 -1
  50. package/dist/types/current/streaming/buffering/BufferingController.js +0 -13
  51. package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.d.ts.map +1 -1
  52. package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.js +13 -4
  53. package/dist/types/current/streaming/mediaTimelineFilter.d.ts +4 -2
  54. package/dist/types/current/streaming/mediaTimelineFilter.d.ts.map +1 -1
  55. package/dist/types/current/streaming/mediaTimelineFilter.js +23 -8
  56. package/dist/types/current/text/SidecarTextTrackLoader.d.ts.map +1 -1
  57. package/dist/types/current/text/SidecarTextTrackLoader.js +4 -4
  58. package/dist/types/current/text/TextTrack.d.ts.map +1 -1
  59. package/dist/types/current/track/TrackController.d.ts.map +1 -1
  60. package/dist/types/current/track/TrackController.js +8 -2
  61. package/dist/types/current/track/createSegmentDataProvider.d.ts.map +1 -1
  62. package/dist/types/current/track/createSegmentDataProvider.js +6 -3
  63. package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
  64. package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.d.ts.map +1 -1
  65. package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.js +13 -8
  66. package/dist/types/current/track/dash/createDashFactories.d.ts +1 -1
  67. package/dist/types/current/track/dash/timeline/DashRepresentationSegmentProvider.d.ts.map +1 -1
  68. package/dist/types/current/track/dash/timeline/DashRepresentationSegmentProvider.js +71 -3
  69. package/dist/types/current/track/dash/urlDashManifestProvider.js +2 -2
  70. package/dist/types/current/track/dash/util/mpd.d.ts +6 -0
  71. package/dist/types/current/track/dash/util/mpd.d.ts.map +1 -1
  72. package/dist/types/current/track/dash/util/mpd.js +24 -3
  73. package/dist/types/current/track/filters/resourceTypeFilter.d.ts +4 -0
  74. package/dist/types/current/track/filters/resourceTypeFilter.d.ts.map +1 -1
  75. package/dist/types/current/track/filters/resourceTypeFilter.js +4 -0
  76. package/dist/types/current/track/filters/sampleRateFilter.d.ts +25 -7
  77. package/dist/types/current/track/filters/sampleRateFilter.d.ts.map +1 -1
  78. package/dist/types/current/track/filters/sampleRateFilter.js +52 -24
  79. package/dist/types/current/track/hls/createHlsFactories.d.ts +1 -1
  80. package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts.map +1 -1
  81. package/dist/types/current/track/hls/createUrlHlsManifestProvider.js +3 -3
  82. package/dist/types/current/track/mse/MseTrack.d.ts +0 -17
  83. package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
  84. package/dist/types/current/track/mse/MseTrack.js +1 -45
  85. package/dist/types/current/tsconfig.tsbuildinfo +1 -1
  86. package/dist/types/current/util/media/mimeType.d.ts +8 -0
  87. package/dist/types/current/util/media/mimeType.d.ts.map +1 -0
  88. package/dist/types/current/util/media/mimeType.js +13 -0
  89. package/dist/types/current/vinyl/VinylPlayer.d.ts +5 -0
  90. package/dist/types/current/vinyl/VinylPlayer.d.ts.map +1 -1
  91. package/dist/types/current/vinyl/VinylPlayer.js +23 -1
  92. package/dist/types/current/vinyl/createVinylFactories.d.ts +7 -16
  93. package/dist/types/current/vinyl/createVinylFactories.d.ts.map +1 -1
  94. package/dist/types/current/vinyl/createVinylFactories.js +2 -12
  95. package/dist/types/ts3.7/drm/DrmControllerImpl.d.ts +19 -1
  96. package/dist/types/ts3.7/index.d.ts +3 -6
  97. package/dist/types/ts3.7/playback/PlaybackControllerImpl.d.ts +15 -0
  98. package/dist/types/ts3.7/playback/ReadonlyPlaybackController.d.ts +28 -0
  99. package/dist/types/ts3.7/playback/createStallDetector.d.ts +43 -0
  100. package/dist/types/ts3.7/playback/createStallMonitor.d.ts +45 -0
  101. package/dist/types/ts3.7/streaming/AudioOptions.d.ts +27 -0
  102. package/dist/types/ts3.7/streaming/ContentStream.d.ts +0 -5
  103. package/dist/types/ts3.7/streaming/LoadMetric.d.ts +15 -1
  104. package/dist/types/ts3.7/streaming/StreamingEventMap.d.ts +0 -5
  105. package/dist/types/ts3.7/streaming/buffering/BufferingController.d.ts +0 -11
  106. package/dist/types/ts3.7/streaming/mediaTimelineFilter.d.ts +4 -2
  107. package/dist/types/ts3.7/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
  108. package/dist/types/ts3.7/track/dash/createDashFactories.d.ts +1 -1
  109. package/dist/types/ts3.7/track/dash/util/mpd.d.ts +6 -0
  110. package/dist/types/ts3.7/track/filters/resourceTypeFilter.d.ts +4 -0
  111. package/dist/types/ts3.7/track/filters/sampleRateFilter.d.ts +25 -7
  112. package/dist/types/ts3.7/track/hls/createHlsFactories.d.ts +1 -1
  113. package/dist/types/ts3.7/track/mse/MseTrack.d.ts +0 -17
  114. package/dist/types/ts3.7/util/media/mimeType.d.ts +8 -0
  115. package/dist/types/ts3.7/vinyl/VinylPlayer.d.ts +5 -0
  116. package/dist/types/ts3.7/vinyl/createVinylFactories.d.ts +7 -16
  117. package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +19 -1
  118. package/dist/types/ts4.1/index.d.ts +3 -6
  119. package/dist/types/ts4.1/playback/PlaybackControllerImpl.d.ts +15 -0
  120. package/dist/types/ts4.1/playback/ReadonlyPlaybackController.d.ts +28 -0
  121. package/dist/types/ts4.1/playback/createStallDetector.d.ts +43 -0
  122. package/dist/types/ts4.1/playback/createStallMonitor.d.ts +45 -0
  123. package/dist/types/ts4.1/streaming/AudioOptions.d.ts +27 -0
  124. package/dist/types/ts4.1/streaming/ContentStream.d.ts +0 -5
  125. package/dist/types/ts4.1/streaming/LoadMetric.d.ts +15 -1
  126. package/dist/types/ts4.1/streaming/StreamingEventMap.d.ts +0 -5
  127. package/dist/types/ts4.1/streaming/buffering/BufferingController.d.ts +0 -11
  128. package/dist/types/ts4.1/streaming/mediaTimelineFilter.d.ts +4 -2
  129. package/dist/types/ts4.1/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
  130. package/dist/types/ts4.1/track/dash/createDashFactories.d.ts +1 -1
  131. package/dist/types/ts4.1/track/dash/util/mpd.d.ts +6 -0
  132. package/dist/types/ts4.1/track/filters/resourceTypeFilter.d.ts +4 -0
  133. package/dist/types/ts4.1/track/filters/sampleRateFilter.d.ts +25 -7
  134. package/dist/types/ts4.1/track/hls/createHlsFactories.d.ts +1 -1
  135. package/dist/types/ts4.1/track/mse/MseTrack.d.ts +0 -17
  136. package/dist/types/ts4.1/util/media/mimeType.d.ts +8 -0
  137. package/dist/types/ts4.1/vinyl/VinylPlayer.d.ts +5 -0
  138. package/dist/types/ts4.1/vinyl/createVinylFactories.d.ts +7 -16
  139. package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +19 -1
  140. package/dist/types/ts4.7/index.d.ts +3 -6
  141. package/dist/types/ts4.7/playback/PlaybackControllerImpl.d.ts +15 -0
  142. package/dist/types/ts4.7/playback/ReadonlyPlaybackController.d.ts +28 -0
  143. package/dist/types/ts4.7/playback/createStallDetector.d.ts +43 -0
  144. package/dist/types/ts4.7/playback/createStallMonitor.d.ts +45 -0
  145. package/dist/types/ts4.7/streaming/AudioOptions.d.ts +27 -0
  146. package/dist/types/ts4.7/streaming/ContentStream.d.ts +0 -5
  147. package/dist/types/ts4.7/streaming/LoadMetric.d.ts +15 -1
  148. package/dist/types/ts4.7/streaming/StreamingEventMap.d.ts +0 -5
  149. package/dist/types/ts4.7/streaming/buffering/BufferingController.d.ts +0 -11
  150. package/dist/types/ts4.7/streaming/mediaTimelineFilter.d.ts +4 -2
  151. package/dist/types/ts4.7/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
  152. package/dist/types/ts4.7/track/dash/createDashFactories.d.ts +1 -1
  153. package/dist/types/ts4.7/track/dash/util/mpd.d.ts +6 -0
  154. package/dist/types/ts4.7/track/filters/resourceTypeFilter.d.ts +4 -0
  155. package/dist/types/ts4.7/track/filters/sampleRateFilter.d.ts +25 -7
  156. package/dist/types/ts4.7/track/hls/createHlsFactories.d.ts +1 -1
  157. package/dist/types/ts4.7/track/mse/MseTrack.d.ts +0 -17
  158. package/dist/types/ts4.7/util/media/mimeType.d.ts +8 -0
  159. package/dist/types/ts4.7/vinyl/VinylPlayer.d.ts +5 -0
  160. package/dist/types/ts4.7/vinyl/createVinylFactories.d.ts +7 -16
  161. package/dist/unit/types/current/ad/AdControllerImpl.test.js +27 -1
  162. package/dist/unit/types/current/drm/DrmControllerImpl.test.js +146 -9
  163. package/dist/unit/types/current/index.d.ts +3 -4
  164. package/dist/unit/types/current/index.d.ts.map +1 -1
  165. package/dist/unit/types/current/index.js +3 -4
  166. package/dist/unit/types/current/playback/PlaybackControllerImpl.test.js +165 -0
  167. package/dist/unit/types/current/playback/createStallDetector.test.d.ts +2 -0
  168. package/dist/unit/types/current/playback/createStallDetector.test.d.ts.map +1 -0
  169. package/dist/unit/types/current/playback/createStallDetector.test.js +163 -0
  170. package/dist/unit/types/current/playback/createStallMonitor.test.d.ts +2 -0
  171. package/dist/unit/types/current/playback/createStallMonitor.test.d.ts.map +1 -0
  172. package/dist/unit/types/current/playback/createStallMonitor.test.js +125 -0
  173. package/dist/unit/types/current/streaming/ContentStream.test.js +0 -11
  174. package/dist/unit/types/current/streaming/MseTrack.test.js +49 -73
  175. package/dist/unit/types/current/streaming/abr/QualitySelectorImpl.test.js +1 -1
  176. package/dist/unit/types/current/streaming/buffering/BufferingController.test.js +0 -21
  177. package/dist/unit/types/current/streaming/createDefaultMediaTimelineTransformer.test.js +133 -0
  178. package/dist/unit/types/current/streaming/mediaTimelineFilter.test.js +60 -54
  179. package/dist/unit/types/current/track/TrackController.test.js +61 -1
  180. package/dist/unit/types/current/track/createSegmentDataProvider.test.js +18 -0
  181. package/dist/unit/types/current/track/dash/DashMediaQualityMetadataResolver.test.js +46 -14
  182. package/dist/unit/types/current/track/dash/createSegmentControllerFactory.test.js +1 -0
  183. package/dist/unit/types/current/track/dash/timeline/DashRepresentationSegmentProvider.test.js +114 -0
  184. package/dist/unit/types/current/track/dash/util/mpd.test.js +37 -0
  185. package/dist/unit/types/current/track/filters/resourceTypeFilter.test.js +7 -0
  186. package/dist/unit/types/current/track/filters/sampleRateFilter.test.js +211 -34
  187. package/dist/unit/types/current/track/source/SourceObjectTrack.test.js +1 -1
  188. package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
  189. package/dist/unit/types/current/util/media/mimeType.test.d.ts +2 -0
  190. package/dist/unit/types/current/util/media/mimeType.test.d.ts.map +1 -0
  191. package/dist/unit/types/current/util/media/mimeType.test.js +22 -0
  192. package/dist/unit/types/current/vinyl/VinylPlayer.test.js +33 -2
  193. package/dist/unit/types/current/vinyl/createVinylFactories.test.js +2 -36
  194. package/dist/vinylTestUtil/index.js +6 -4
  195. package/dist/vinylTestUtil/index.js.map +2 -2
  196. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts +1 -0
  197. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts.map +1 -1
  198. package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.js +1 -0
  199. package/dist/vinylTestUtil/types/current/media/FrequencyAnalyzer.d.ts.map +1 -1
  200. package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.d.ts +0 -1
  201. package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.d.ts.map +1 -1
  202. package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.js +0 -1
  203. package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
  204. package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.d.ts.map +1 -1
  205. package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.js +0 -1
  206. package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
  207. package/dist/vinylTestUtil/types/current/util/playback/eventPromises.js +1 -1
  208. package/dist/vinylTestUtil/types/current/util/playback/expectTrackCanSeek.d.ts.map +1 -1
  209. package/dist/vinylTestUtil/types/current/util/playback/expectTrackCanSeek.js +9 -1
  210. package/dist/vinylTestUtil/types/ts3.7/assets/vinylTestAssets.d.ts +1 -0
  211. package/dist/vinylTestUtil/types/ts3.7/mock/streaming/MockContentStream.d.ts +0 -1
  212. package/dist/vinylTestUtil/types/ts3.7/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
  213. package/dist/vinylTestUtil/types/ts4.1/assets/vinylTestAssets.d.ts +1 -0
  214. package/dist/vinylTestUtil/types/ts4.1/mock/streaming/MockContentStream.d.ts +0 -1
  215. package/dist/vinylTestUtil/types/ts4.1/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
  216. package/dist/vinylTestUtil/types/ts4.7/assets/vinylTestAssets.d.ts +1 -0
  217. package/dist/vinylTestUtil/types/ts4.7/mock/streaming/MockContentStream.d.ts +0 -1
  218. package/dist/vinylTestUtil/types/ts4.7/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
  219. package/docs/USAGE.md +29 -0
  220. package/package.json +11 -10
  221. package/dist/types/current/patch/media/HtmlMediaElementPatch.d.ts +0 -3
  222. package/dist/types/current/patch/media/HtmlMediaElementPatch.d.ts.map +0 -1
  223. package/dist/types/current/patch/media/HtmlMediaElementPatch.js +0 -5
  224. package/dist/types/current/patch/media/MediaElementPatchOptions.d.ts +0 -19
  225. package/dist/types/current/patch/media/MediaElementPatchOptions.d.ts.map +0 -1
  226. package/dist/types/current/patch/media/MediaElementPatchOptions.js +0 -16
  227. package/dist/types/current/patch/media/patchMediaElement.d.ts +0 -9
  228. package/dist/types/current/patch/media/patchMediaElement.d.ts.map +0 -1
  229. package/dist/types/current/patch/media/patchMediaElement.js +0 -15
  230. package/dist/types/current/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
  231. package/dist/types/current/patch/media/patches/createPreventStallsPatch.d.ts.map +0 -1
  232. package/dist/types/current/patch/media/patches/createPreventStallsPatch.js +0 -55
  233. package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
  234. package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts.map +0 -1
  235. package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.js +0 -218
  236. package/dist/types/current/vinyl/VinylPatchOptions.d.ts +0 -10
  237. package/dist/types/current/vinyl/VinylPatchOptions.d.ts.map +0 -1
  238. package/dist/types/current/vinyl/VinylPatchOptions.js +0 -11
  239. package/dist/types/ts3.7/patch/media/HtmlMediaElementPatch.d.ts +0 -3
  240. package/dist/types/ts3.7/patch/media/MediaElementPatchOptions.d.ts +0 -19
  241. package/dist/types/ts3.7/patch/media/patchMediaElement.d.ts +0 -9
  242. package/dist/types/ts3.7/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
  243. package/dist/types/ts3.7/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
  244. package/dist/types/ts3.7/vinyl/VinylPatchOptions.d.ts +0 -10
  245. package/dist/types/ts4.1/patch/media/HtmlMediaElementPatch.d.ts +0 -3
  246. package/dist/types/ts4.1/patch/media/MediaElementPatchOptions.d.ts +0 -19
  247. package/dist/types/ts4.1/patch/media/patchMediaElement.d.ts +0 -9
  248. package/dist/types/ts4.1/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
  249. package/dist/types/ts4.1/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
  250. package/dist/types/ts4.1/vinyl/VinylPatchOptions.d.ts +0 -10
  251. package/dist/types/ts4.7/patch/media/HtmlMediaElementPatch.d.ts +0 -3
  252. package/dist/types/ts4.7/patch/media/MediaElementPatchOptions.d.ts +0 -19
  253. package/dist/types/ts4.7/patch/media/patchMediaElement.d.ts +0 -9
  254. package/dist/types/ts4.7/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
  255. package/dist/types/ts4.7/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
  256. package/dist/types/ts4.7/vinyl/VinylPatchOptions.d.ts +0 -10
  257. package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.d.ts +0 -2
  258. package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.d.ts.map +0 -1
  259. package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.js +0 -17
  260. package/dist/unit/types/current/patch/media/patchMediaElement.test.d.ts +0 -2
  261. package/dist/unit/types/current/patch/media/patchMediaElement.test.d.ts.map +0 -1
  262. package/dist/unit/types/current/patch/media/patchMediaElement.test.js +0 -36
  263. package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.d.ts +0 -2
  264. package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.d.ts.map +0 -1
  265. package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.js +0 -93
  266. package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.d.ts +0 -2
  267. package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.d.ts.map +0 -1
  268. package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.js +0 -423
@@ -2,9 +2,11 @@
2
2
  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { Abort, AbortError, Browser, clamp, closeTo, createDisposer, createTimeRangesReader, DomEventHost, ErrorLevel, ErrorOrigin, EventHostImpl, getUserAgentInfo, hasBrowser, logDebug, logInfo, logWarn, nextEventAsPromise, onAny, ownKeys, TimeoutSlot, toLowerCase, withAbort, withTimeout, } from '@amazon/vinyl-util';
5
+ import { Abort, AbortError, Browser, clamp, closeTo, createDisposer, createTimeRangesReader, DomEventHost, ErrorLevel, ErrorOrigin, EventHostImpl, hasBrowser, logDebug, logInfo, logWarn, nextEventAsPromise, noop, onAny, ownKeys, TimeoutSlot, toLowerCase, withAbort, withTimeout, } from '@amazon/vinyl-util';
6
6
  import { LIVE_DURATION } from './PlaybackController';
7
7
  import { PlaybackReadyState, playedReasons, waitedReasons, } from './ReadonlyPlaybackController';
8
+ import { createStallDetector } from './createStallDetector';
9
+ import { createStallMonitor } from './createStallMonitor';
8
10
  import { InvalidSeekError } from './error/InvalidSeekError';
9
11
  import { playbackStateLoggingHandler } from './logging/playbackStateLoggingHandler';
10
12
  import { createChangeEventTrigger } from '../event/ChangeEvent';
@@ -15,6 +17,7 @@ export const defaultPlaybackControllerImplOptions = {
15
17
  minSeekableBuffer: -1, // Default comes from getMinSeekableBufferDefault()
16
18
  playTimeout: 30,
17
19
  seekTimeout: 30,
20
+ stallThreshold: 1,
18
21
  };
19
22
  /**
20
23
  * Safari and Edge Legacy cannot reliably seek close to the end of a seekable range.
@@ -24,6 +27,19 @@ export const defaultPlaybackControllerImplOptions = {
24
27
  export function getMinSeekableBufferDefault() {
25
28
  return !hasBrowser(Browser.CHROMIUM) ? 5 : 2;
26
29
  }
30
+ /**
31
+ * Re-issue a seek up to this many times when it lands outside tolerance of the target (some WebKit
32
+ * versions report `seeked` at the pre-seek position) or when it never completes (see
33
+ * {@link SEEK_ATTEMPT_TIMEOUT}).
34
+ */
35
+ const SEEK_RETRY_LIMIT = 2;
36
+ /**
37
+ * Seconds to wait for `seeked` before treating a seek attempt as stuck. WebKit can leave a seek on
38
+ * a freshly-attached, never-decoded source pending forever (observed on iOS Safari); on a stuck
39
+ * attempt the decoder is warmed with play() and the seek re-issued. Comfortably above a normal
40
+ * seek's completion time so a slow-but-valid seek isn't cut short.
41
+ */
42
+ const SEEK_ATTEMPT_TIMEOUT = 4;
27
43
  export class PlaybackControllerImpl extends EventHostImpl {
28
44
  loudnessNormalizationController;
29
45
  get [Symbol.toStringTag]() {
@@ -38,6 +54,8 @@ export class PlaybackControllerImpl extends EventHostImpl {
38
54
  _waiting = false;
39
55
  // Monotonic counter identifying the most recent seekTo call.
40
56
  _seekId = 0;
57
+ // Stops the stall detector started after the last seek, if any.
58
+ stopStallDetection = null;
41
59
  // default user volume (0.0 to 1.0 scale)
42
60
  userVolume = 1.0;
43
61
  _pendingPlay = null;
@@ -59,6 +77,7 @@ export class PlaybackControllerImpl extends EventHostImpl {
59
77
  add(() => {
60
78
  this.disposeAbort.abort();
61
79
  this.disposeAbort.dispose();
80
+ this.stopStallDetection?.();
62
81
  });
63
82
  this.domEvents = add(new DomEventHost(deps.media));
64
83
  // Log media events.
@@ -78,6 +97,12 @@ export class PlaybackControllerImpl extends EventHostImpl {
78
97
  });
79
98
  });
80
99
  this.initializeEvents();
100
+ // Detect and report play-head stalls (frozen while playing) via
101
+ // stallEntered/stallEnded, independent of the recovery stall detector.
102
+ add(createStallMonitor(this, {
103
+ onStallEntered: () => this.dispatch('stallEntered', {}),
104
+ onStallEnded: (info) => this.dispatch('stallEnded', info),
105
+ }, { stallThreshold: this.options.stallThreshold }));
81
106
  // Listen for loudness normalization changes
82
107
  add(this.loudnessNormalizationController.on('change', () => {
83
108
  this.refreshVolume();
@@ -278,7 +303,7 @@ export class PlaybackControllerImpl extends EventHostImpl {
278
303
  !this.paused &&
279
304
  this.currentTime <= this.options.loopTimeAffordance &&
280
305
  lastTime >= this.duration - this.options.loopTimeAffordance) {
281
- if (getUserAgentInfo().browserLike?.name === Browser.FIREFOX) {
306
+ if (hasBrowser(Browser.FIREFOX)) {
282
307
  // Firefox does not emit a 'playing' event after a loop, fabricate one.
283
308
  logDebug(this, 'fabricating Firefox playing event on loop');
284
309
  this.dispatch('playing', {});
@@ -388,8 +413,27 @@ export class PlaybackControllerImpl extends EventHostImpl {
388
413
  current: value != null,
389
414
  });
390
415
  }
416
+ /**
417
+ * Starts (or restarts) the stall detector: recovers a play head that stays frozen when it
418
+ * should be advancing — some WebKit versions never resume after a seek or a play, e.g. after a
419
+ * buffer-flushing seek. Superseded by the next seek/play and stopped on dispose.
420
+ * @private
421
+ */
422
+ startStallDetection() {
423
+ this.stopStallDetection?.();
424
+ this.stopStallDetection = createStallDetector(this.media, {
425
+ fabricatePlaying: () => this.dispatch('playing', {}),
426
+ // Safari/iOS can leave a seek on a freshly-attached source pending
427
+ // with a stale empty `buffered`; re-issue the seek to un-stick it.
428
+ nudgeUnbuffered: hasBrowser(Browser.WEBKIT),
429
+ });
430
+ }
391
431
  play() {
392
432
  logDebug(this, 'play');
433
+ // Recover a play that never starts progressing (stuck WebKit) — start
434
+ // even when a prior play() is still pending, e.g. a resume that stalled
435
+ // after a buffer-flushing seek, whose play() promise this call returns.
436
+ this.startStallDetection();
393
437
  if (!this.pendingPlay) {
394
438
  const playAbort = new Abort();
395
439
  this.playAbort = playAbort;
@@ -430,15 +474,18 @@ export class PlaybackControllerImpl extends EventHostImpl {
430
474
  // Claim this seek as the newest. A seek that parks on an await (e.g. waiting for loadedMetadata) can resume
431
475
  // AFTER a later-issued seek has already applied its target.
432
476
  const seekId = ++this._seekId;
477
+ // A new seek supersedes any stall watch from the previous one.
478
+ this.stopStallDetection?.();
479
+ this.stopStallDetection = null;
433
480
  const superseded = () => {
434
481
  if (this._seekId === seekId)
435
482
  return false;
436
483
  logDebug(this, `seekTo: ${time} superseded by a newer seek`);
437
484
  return true;
438
485
  };
439
- const nextEvent = (type) => {
486
+ const nextEvent = (type, options) => {
440
487
  return nextEventAsPromise(this, type, {
441
- timeout: this.options.seekTimeout,
488
+ timeout: options?.timeout ?? this.options.seekTimeout,
442
489
  timeoutMessage: `seek timed out on ${type} event after {time}s`,
443
490
  timeoutOrigin: ErrorOrigin.INTERNAL,
444
491
  timeoutLevel: ErrorLevel.WARN,
@@ -471,14 +518,58 @@ export class PlaybackControllerImpl extends EventHostImpl {
471
518
  logDebug(this, `seekTo: requested: ${time}, actual: ${clampedTime} no-op`);
472
519
  }
473
520
  else {
474
- const seeking = nextEvent('seeking');
475
- this.media.currentTime = clampedTime;
476
- await seeking;
477
- const seekedEvent = await nextEvent('seeked');
478
- if (seekedEvent.reason === 'emptied') {
521
+ // A dispose rejects the seek; a timeout means the attempt is stuck.
522
+ const nullIfStuck = (error) => {
523
+ if (this.disposeAbort.aborted())
524
+ throw error;
525
+ return null;
526
+ };
527
+ // Issues one seek attempt. Returns the seeked event, or null if it
528
+ // didn't complete within SEEK_ATTEMPT_TIMEOUT. Observed on WebKit/iOS
529
+ // Safari: seeking a freshly-attached source that has never decoded a
530
+ // frame hangs — the seek never completes and playback stalls in
531
+ // `waiting`. The initial attempt keeps the full seekTimeout on
532
+ // `seeking` (a media that never starts seeking is a real timeout); a
533
+ // re-seek to the current position emits no fresh `seeking` on WebKit,
534
+ // so bound that wait and fall through to the stall detector.
535
+ const seekOnce = async (reSeeking) => {
536
+ const seeking = reSeeking
537
+ ? nextEvent('seeking', {
538
+ timeout: SEEK_ATTEMPT_TIMEOUT,
539
+ }).catch(nullIfStuck)
540
+ : nextEvent('seeking');
541
+ this.media.currentTime = clampedTime;
542
+ if ((await seeking) === null)
543
+ return null;
544
+ return nextEvent('seeked', {
545
+ timeout: SEEK_ATTEMPT_TIMEOUT,
546
+ }).catch(nullIfStuck);
547
+ };
548
+ let seekedEvent = await seekOnce(false);
549
+ // Re-issue the seek when it never completed (kick the decoder with
550
+ // play() first — decoding a frame unsticks WebKit) or when it landed
551
+ // outside tolerance of the target. Bounded, and not fighting a newer
552
+ // seek. The play() kick is fire-and-forget: a wedged seek can leave
553
+ // its promise unsettled forever, which would stall the retry loop.
554
+ for (let attempt = 0; !superseded() &&
555
+ attempt < SEEK_RETRY_LIMIT &&
556
+ (seekedEvent === null ||
557
+ (seekedEvent.reason === 'seeked' &&
558
+ !closeTo(clampedTime, this.currentTime, tolerance))); attempt++) {
559
+ if (seekedEvent === null)
560
+ void this.media.play().catch(noop);
561
+ logDebug(this, `seekTo: re-seeking to ${clampedTime}`);
562
+ seekedEvent = await seekOnce(true);
563
+ }
564
+ if (seekedEvent?.reason === 'emptied') {
479
565
  // Seek finished due to track unloading. Abort any pending seek.
480
566
  throw new AbortError();
481
567
  }
568
+ // Some WebKit versions don't resume after seeking. Watch for that:
569
+ // nudge/kick playback back to life, and fabricate a playing event
570
+ // if it resumes without emitting one.
571
+ if (!this.paused)
572
+ this.startStallDetection();
482
573
  }
483
574
  }
484
575
  /**
@@ -188,6 +188,19 @@ export interface PlaybackControllerEventMap {
188
188
  * true and the media is seeking a new position.
189
189
  */
190
190
  readonly seeking: AnyRecord;
191
+ /**
192
+ * Fired when the play head has been detected as frozen while playing — i.e. no `timeUpdate`
193
+ * was observed for longer than the stall threshold. Only emitted once playback has actually
194
+ * been observed (a `playing` event), so it is not emitted during initial loading or while
195
+ * awaiting a seek. Each stall is bracketed by a `stallEntered`/{@link stallEnded} pair.
196
+ */
197
+ readonly stallEntered: AnyRecord;
198
+ /**
199
+ * Fired when a stall (see {@link stallEntered}) has ended, for any reason. `started` is the
200
+ * time of the last `timeUpdate` observed before the play head froze, and `duration` is the
201
+ * total number of seconds the play head was frozen.
202
+ */
203
+ readonly stallEnded: OperationCompletedEvent<StallEndedReason>;
191
204
  /**
192
205
  * Fired when the time indicated by the currentTime attribute has been updated.
193
206
  */
@@ -230,7 +243,20 @@ export declare enum WaitedReason {
230
243
  * A list of all events that cause the 2nd-order 'waited' event.
231
244
  */
232
245
  export declare const waitedReasons: readonly [WaitedReason.PLAYING, WaitedReason.PAUSE, WaitedReason.EMPTIED, WaitedReason.SEEKING];
233
- export declare const ALL_PLAYBACK_STATE_EVENTS: readonly ["abort", "canPlay", "canPlayThrough", "durationChange", "emptied", "ended", "error", "playIsPendingChange", "loadedData", "loadedMetadata", "loadStart", "looped", "mutedChange", "pause", "play", "playRejected", "played", "playing", "progress", "rateChange", "readyStateChange", "reset", "resize", "seeked", "seeking", "timeUpdate", "volumeChange", "waited", "waiting", "waitingForKey"];
246
+ /**
247
+ * Reasons a stall (see {@link PlaybackControllerEventMap.stallEntered}) has ended.
248
+ */
249
+ export declare enum StallEndedReason {
250
+ /** The play head advanced again — playback resumed. */
251
+ PLAYING = "playing",
252
+ /** Playback was paused. */
253
+ PAUSE = "pause",
254
+ /** A seek started. */
255
+ SEEKING = "seeking",
256
+ /** The media was emptied (e.g. unloaded/reloaded). */
257
+ EMPTIED = "emptied"
258
+ }
259
+ export declare const ALL_PLAYBACK_STATE_EVENTS: readonly ["abort", "canPlay", "canPlayThrough", "durationChange", "emptied", "ended", "error", "playIsPendingChange", "loadedData", "loadedMetadata", "loadStart", "looped", "mutedChange", "pause", "play", "playRejected", "played", "playing", "progress", "rateChange", "readyStateChange", "reset", "resize", "seeked", "seeking", "stallEntered", "stallEnded", "timeUpdate", "volumeChange", "waited", "waiting", "waitingForKey"];
234
260
  export declare enum PlaybackReadyState {
235
261
  /**
236
262
  * No information is available about the media resource.
@@ -1 +1 @@
1
- {"version":3,"file":"ReadonlyPlaybackController.d.ts","sourceRoot":"","sources":["../../../../src/playback/ReadonlyPlaybackController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ReadonlyPlaybackController.d.ts","sourceRoot":"","sources":["../../../../src/playback/ReadonlyPlaybackController.ts"],"names":[],"mappings":"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"}
@@ -48,6 +48,20 @@ export const waitedReasons = [
48
48
  WaitedReason.EMPTIED,
49
49
  WaitedReason.SEEKING,
50
50
  ];
51
+ /**
52
+ * Reasons a stall (see {@link PlaybackControllerEventMap.stallEntered}) has ended.
53
+ */
54
+ export var StallEndedReason;
55
+ (function (StallEndedReason) {
56
+ /** The play head advanced again — playback resumed. */
57
+ StallEndedReason["PLAYING"] = "playing";
58
+ /** Playback was paused. */
59
+ StallEndedReason["PAUSE"] = "pause";
60
+ /** A seek started. */
61
+ StallEndedReason["SEEKING"] = "seeking";
62
+ /** The media was emptied (e.g. unloaded/reloaded). */
63
+ StallEndedReason["EMPTIED"] = "emptied";
64
+ })(StallEndedReason || (StallEndedReason = {}));
51
65
  export const ALL_PLAYBACK_STATE_EVENTS = [
52
66
  'abort',
53
67
  'canPlay',
@@ -74,6 +88,8 @@ export const ALL_PLAYBACK_STATE_EVENTS = [
74
88
  'resize',
75
89
  'seeked',
76
90
  'seeking',
91
+ 'stallEntered',
92
+ 'stallEnded',
77
93
  'timeUpdate',
78
94
  'volumeChange',
79
95
  'waited',
@@ -0,0 +1,43 @@
1
+ export interface StallDetectorOptions {
2
+ /**
3
+ * Called when playback resumes without emitting a `playing` event (or after the detector
4
+ * revived it), so the caller can announce playback.
5
+ */
6
+ readonly fabricatePlaying: () => void;
7
+ /** Poll interval in seconds. Default: 0.25 */
8
+ readonly pollInterval?: number;
9
+ /** Seconds the play head must be frozen before recovering. Default: 0.75 */
10
+ readonly stallThreshold?: number;
11
+ /**
12
+ * Don't nudge this close to the end of buffered data; a freeze there is a
13
+ * legitimate wait for more data. Seconds. Default: 0.5
14
+ */
15
+ readonly bufferEndMargin?: number;
16
+ /** Seconds to nudge the play head forward to prod a resume. Default: 0.1 */
17
+ readonly stallSkip?: number;
18
+ /** Max recovery attempts before giving up. Default: 3 */
19
+ readonly maxAttempts?: number;
20
+ /**
21
+ * Nudge even when the playhead isn't inside a buffered range. Some WebKit versions leave a
22
+ * seek perpetually pending (no `seeked`) with a stale empty `buffered` even though data is
23
+ * appended; re-issuing the seek via a nudge can complete it. Off by default (only nudge over
24
+ * genuinely buffered data). Default: false
25
+ */
26
+ readonly nudgeUnbuffered?: boolean;
27
+ }
28
+ /**
29
+ * Watches a media element after a seek/play and drives it back into a playing state. Some WebKit
30
+ * versions stall: the play head freezes inside buffered data and playback never resumes (often with
31
+ * a stale low readyState). This polls the play head and, while the element should be advancing:
32
+ *
33
+ * - if it's frozen inside buffered data, nudges currentTime forward to prod a resume;
34
+ * - if there's no data at the playhead, waits (it's legitimately buffering, or between sources).
35
+ *
36
+ * When the play head progresses it calls {@link StallDetectorOptions.fabricatePlaying} if playback
37
+ * resumed without a `playing` event (or after the detector revived it), then stops. It also stops
38
+ * on pause/ended/emptied or after {@link StallDetectorOptions.maxAttempts}.
39
+ *
40
+ * Start it after a seek or play only when playback is intended — a paused element won't advance.
41
+ */
42
+ export declare function createStallDetector(media: HTMLMediaElement, options: StallDetectorOptions): () => void;
43
+ //# sourceMappingURL=createStallDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createStallDetector.d.ts","sourceRoot":"","sources":["../../../../src/playback/createStallDetector.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACjC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,IAAI,CAAA;IACrC,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,oBAAoB,GAC9B,MAAM,IAAI,CAgEZ"}
@@ -0,0 +1,101 @@
1
+ /*
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Watches a media element after a seek/play and drives it back into a playing state. Some WebKit
7
+ * versions stall: the play head freezes inside buffered data and playback never resumes (often with
8
+ * a stale low readyState). This polls the play head and, while the element should be advancing:
9
+ *
10
+ * - if it's frozen inside buffered data, nudges currentTime forward to prod a resume;
11
+ * - if there's no data at the playhead, waits (it's legitimately buffering, or between sources).
12
+ *
13
+ * When the play head progresses it calls {@link StallDetectorOptions.fabricatePlaying} if playback
14
+ * resumed without a `playing` event (or after the detector revived it), then stops. It also stops
15
+ * on pause/ended/emptied or after {@link StallDetectorOptions.maxAttempts}.
16
+ *
17
+ * Start it after a seek or play only when playback is intended — a paused element won't advance.
18
+ */
19
+ export function createStallDetector(media, options) {
20
+ const { fabricatePlaying } = options;
21
+ const pollInterval = options.pollInterval ?? 0.25;
22
+ const stallThreshold = options.stallThreshold ?? 0.75;
23
+ const bufferEndMargin = options.bufferEndMargin ?? 0.5;
24
+ const stallSkip = options.stallSkip ?? 0.1;
25
+ const maxAttempts = options.maxAttempts ?? 3;
26
+ const nudgeUnbuffered = options.nudgeUnbuffered ?? false;
27
+ let lastTime = media.currentTime;
28
+ let stalledFor = 0;
29
+ let attempts = 0;
30
+ let sawPlaying = false;
31
+ let recovered = false;
32
+ const onPlaying = () => {
33
+ sawPlaying = true;
34
+ };
35
+ // A cleared source (track unload) means there's nothing to recover.
36
+ const onEmptied = () => stop();
37
+ media.addEventListener('playing', onPlaying);
38
+ media.addEventListener('emptied', onEmptied);
39
+ const stop = () => {
40
+ clearInterval(id);
41
+ media.removeEventListener('playing', onPlaying);
42
+ media.removeEventListener('emptied', onEmptied);
43
+ };
44
+ const id = setInterval(() => {
45
+ // Only a playing element is expected to advance.
46
+ if (media.paused || media.ended)
47
+ return stop();
48
+ if (media.currentTime !== lastTime) {
49
+ lastTime = media.currentTime;
50
+ // Progressing. Announce playback if it resumed without a `playing`
51
+ // event, or if the detector had to revive it (a prior `playing` was
52
+ // cleared by the intervening stall).
53
+ if (!sawPlaying || recovered)
54
+ fabricatePlaying();
55
+ return stop();
56
+ }
57
+ // Frozen. Recover only over buffered data; otherwise it's legitimately
58
+ // waiting to buffer (or between sources) and nudging can disrupt
59
+ // loading — except on platforms whose `buffered` is unreliable here
60
+ // ({@link nudgeUnbuffered}), where re-issuing the seek is the recovery.
61
+ if (!nudgeUnbuffered && !withinBufferedData(media, bufferEndMargin)) {
62
+ stalledFor = 0;
63
+ return;
64
+ }
65
+ stalledFor += pollInterval;
66
+ if (stalledFor < stallThreshold)
67
+ return;
68
+ stalledFor = 0;
69
+ if (attempts++ >= maxAttempts)
70
+ return stop();
71
+ recovered = true;
72
+ // Data is buffered at the playhead — a small nudge prods a resume.
73
+ // (iOS can sit here reporting a stale low readyState even though the
74
+ // range around the playhead is buffered.)
75
+ media.currentTime += stallSkip;
76
+ lastTime = media.currentTime;
77
+ }, pollInterval * 1000);
78
+ return stop;
79
+ }
80
+ /**
81
+ * True when the play head sits inside a buffered range, clear of its end by
82
+ * {@link endMargin} — i.e. there is data to keep playing.
83
+ *
84
+ * A range that ends at the end of the media is treated as nudgeable up to its
85
+ * end: no more data is coming, so a freeze there is a stuck end-of-track (seen
86
+ * on progressive `src` tracks), and a nudge pushes the element to `ended`.
87
+ */
88
+ function withinBufferedData(media, endMargin) {
89
+ const t = media.currentTime;
90
+ const duration = media.duration;
91
+ const buffered = media.buffered;
92
+ for (let i = 0; i < buffered.length; i++) {
93
+ const end = buffered.end(i);
94
+ const isEndOfMedia = Number.isFinite(duration) &&
95
+ duration > 0 &&
96
+ end >= duration - endMargin;
97
+ if (t >= buffered.start(i) && (t < end - endMargin || isEndOfMedia))
98
+ return true;
99
+ }
100
+ return false;
101
+ }
@@ -0,0 +1,45 @@
1
+ import { type Timestamp } from '@amazon/vinyl-util';
2
+ import type { ReadonlyPlaybackController } from './ReadonlyPlaybackController';
3
+ import { StallEndedReason } from './ReadonlyPlaybackController';
4
+ export interface StallMonitorOptions {
5
+ /**
6
+ * Seconds the play head must be frozen (no `timeUpdate`) while playing before a stall is
7
+ * reported. Default: 1
8
+ */
9
+ readonly stallThreshold?: number;
10
+ /**
11
+ * How often (seconds) the play head freeze is checked. Default: 0.25
12
+ */
13
+ readonly pollInterval?: number;
14
+ }
15
+ export interface StallEndedInfo {
16
+ /** Time of the last `timeUpdate` observed before the play head froze. */
17
+ readonly started: Timestamp;
18
+ /** Time the stall ended. */
19
+ readonly ended: Timestamp;
20
+ /** Total seconds the play head was frozen. */
21
+ readonly duration: number;
22
+ /** Why the stall ended. */
23
+ readonly reason: StallEndedReason;
24
+ }
25
+ export interface StallMonitorCallbacks {
26
+ /** Called when a stall is first detected. */
27
+ readonly onStallEntered: () => void;
28
+ /** Called when a detected stall ends. */
29
+ readonly onStallEnded: (info: StallEndedInfo) => void;
30
+ }
31
+ /**
32
+ * Watches a {@link ReadonlyPlaybackController} for stalls while playing: the play head freezing
33
+ * (no `timeUpdate`) for longer than {@link StallMonitorOptions.stallThreshold} seconds.
34
+ *
35
+ * Detection only runs once playback has actually been observed (a `playing` event) and is
36
+ * suspended on `pause`/`seeking`/`emptied` until playback is observed again — so a stall is never
37
+ * reported during initial loading or while awaiting a seek. A detected stall ends when the play
38
+ * head advances again (`playing`) or playback stops for another reason (`pause`/`seeking`/
39
+ * `emptied`); {@link StallEndedInfo.duration} is measured from the last `timeUpdate` before the
40
+ * freeze, so it reflects the full frozen time rather than only the time past the threshold.
41
+ *
42
+ * Returns a function that stops the monitor.
43
+ */
44
+ export declare function createStallMonitor(controller: ReadonlyPlaybackController, callbacks: StallMonitorCallbacks, options?: StallMonitorOptions): () => void;
45
+ //# sourceMappingURL=createStallMonitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createStallMonitor.d.ts","sourceRoot":"","sources":["../../../../src/playback/createStallMonitor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAE/D,MAAM,WAAW,mBAAmB;IAChC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAEhC;;OAEG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,cAAc;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;IAC3B,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,2BAA2B;IAC3B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;CACpC;AAED,MAAM,WAAW,qBAAqB;IAClC,6CAA6C;IAC7C,QAAQ,CAAC,cAAc,EAAE,MAAM,IAAI,CAAA;IACnC,yCAAyC;IACzC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;CACxD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAC9B,UAAU,EAAE,0BAA0B,EACtC,SAAS,EAAE,qBAAqB,EAChC,OAAO,GAAE,mBAAwB,GAClC,MAAM,IAAI,CA8EZ"}
@@ -0,0 +1,94 @@
1
+ /*
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { createDisposer } from '@amazon/vinyl-util';
6
+ import { StallEndedReason } from './ReadonlyPlaybackController';
7
+ /**
8
+ * Watches a {@link ReadonlyPlaybackController} for stalls while playing: the play head freezing
9
+ * (no `timeUpdate`) for longer than {@link StallMonitorOptions.stallThreshold} seconds.
10
+ *
11
+ * Detection only runs once playback has actually been observed (a `playing` event) and is
12
+ * suspended on `pause`/`seeking`/`emptied` until playback is observed again — so a stall is never
13
+ * reported during initial loading or while awaiting a seek. A detected stall ends when the play
14
+ * head advances again (`playing`) or playback stops for another reason (`pause`/`seeking`/
15
+ * `emptied`); {@link StallEndedInfo.duration} is measured from the last `timeUpdate` before the
16
+ * freeze, so it reflects the full frozen time rather than only the time past the threshold.
17
+ *
18
+ * Returns a function that stops the monitor.
19
+ */
20
+ export function createStallMonitor(controller, callbacks, options = {}) {
21
+ const stallThresholdMs = (options.stallThreshold ?? 1) * 1000;
22
+ const pollIntervalMs = (options.pollInterval ?? 0.25) * 1000;
23
+ // Only detect once playback has been observed; gates out initial load and post-seek buffering.
24
+ let observedPlaying = false;
25
+ // Wall-clock time of the last `timeUpdate` — the reference start of any freeze.
26
+ let lastProgressAt = Date.now();
27
+ let inStall = false;
28
+ let pollId = null;
29
+ const endStall = (reason) => {
30
+ if (!inStall)
31
+ return;
32
+ inStall = false;
33
+ const ended = Date.now();
34
+ callbacks.onStallEnded({
35
+ started: lastProgressAt,
36
+ ended,
37
+ duration: (ended - lastProgressAt) / 1000,
38
+ reason,
39
+ });
40
+ };
41
+ const stopPoll = () => {
42
+ if (pollId == null)
43
+ return;
44
+ clearInterval(pollId);
45
+ pollId = null;
46
+ };
47
+ const startPoll = () => {
48
+ if (pollId != null)
49
+ return;
50
+ pollId = setInterval(() => {
51
+ if (inStall || !observedPlaying)
52
+ return;
53
+ if (Date.now() - lastProgressAt >= stallThresholdMs) {
54
+ inStall = true;
55
+ callbacks.onStallEntered();
56
+ }
57
+ }, pollIntervalMs);
58
+ };
59
+ // Playback stopped for a non-resume reason: close any open stall and suspend detection until
60
+ // playback is observed again.
61
+ const suspend = (reason) => {
62
+ endStall(reason);
63
+ observedPlaying = false;
64
+ stopPoll();
65
+ };
66
+ const { add, dispose } = createDisposer();
67
+ add(controller.on('playing', () => {
68
+ // Resuming from a stall closes it; otherwise this is a fresh start of playback, so
69
+ // begin the freeze clock now (no `timeUpdate` has necessarily arrived yet).
70
+ if (inStall)
71
+ endStall(StallEndedReason.PLAYING);
72
+ else
73
+ lastProgressAt = Date.now();
74
+ observedPlaying = true;
75
+ startPoll();
76
+ }));
77
+ add(controller.on('timeUpdate', () => {
78
+ // The play head advanced: close any open stall, then reset the freeze clock.
79
+ if (inStall)
80
+ endStall(StallEndedReason.PLAYING);
81
+ lastProgressAt = Date.now();
82
+ }));
83
+ add(controller.on('pause', () => suspend(StallEndedReason.PAUSE)));
84
+ add(controller.on('seeking', () => suspend(StallEndedReason.SEEKING)));
85
+ add(controller.on('emptied', () => suspend(StallEndedReason.EMPTIED)));
86
+ let stopped = false;
87
+ return () => {
88
+ if (stopped)
89
+ return;
90
+ stopped = true;
91
+ stopPoll();
92
+ dispose();
93
+ };
94
+ }
@@ -1,4 +1,5 @@
1
1
  import type { ObjectSchema } from '@amazon/vinyl-validation';
2
+ import type { Maybe } from '@amazon/vinyl-util';
2
3
  /**
3
4
  * Criteria used to pick which audio rendition to play.
4
5
  */
@@ -33,6 +34,32 @@ export interface AudioOptions {
33
34
  * The audio selection criteria (language / descriptive).
34
35
  */
35
36
  readonly selection?: AudioSelection;
37
+ /**
38
+ * When `true`, disables the audio sampling-rate support filter, so audio
39
+ * renditions whose sampling rate exceeds what the platform reports (via the
40
+ * `AudioContext` output rate) are kept and offered to playback anyway.
41
+ * `false` (the default) applies the filter. Changing this re-filters the
42
+ * timeline immediately.
43
+ */
44
+ readonly disableSampleRateFilter?: boolean;
45
+ /**
46
+ * The maximum audio sampling rate (Hz) the sampling-rate filter treats as
47
+ * supported. When set, it takes precedence over the platform's
48
+ * `AudioContext`-reported output rate. Ignored when
49
+ * {@link disableSampleRateFilter} is `true`. Changing this re-filters the
50
+ * timeline immediately. `null`/`undefined` falls back to the reported rate.
51
+ */
52
+ readonly maxSampleRate?: Maybe<number>;
53
+ /**
54
+ * The minimum audio sampling rate (Hz) the sampling-rate filter treats as
55
+ * supported. When set, renditions below it are filtered out as a soft floor
56
+ * (if every audio rendition is below it, the highest is kept so playback is
57
+ * never stranded). Applied alongside the max cap whenever the filter runs,
58
+ * regardless of how the max was determined; ignored when
59
+ * {@link disableSampleRateFilter} is `true`. Changing this re-filters the
60
+ * timeline immediately.
61
+ */
62
+ readonly minSampleRate?: Maybe<number>;
36
63
  }
37
64
  export declare const defaultAudioOptions: AudioOptions;
38
65
  export declare const audioOptionsValidator: ObjectSchema<AudioOptions>;
@@ -1 +1 @@
1
- {"version":3,"file":"AudioOptions.d.ts","sourceRoot":"","sources":["../../../../src/streaming/AudioOptions.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAG5D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;IAErD;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IACzB;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAA;CACtC;AAED,eAAO,MAAM,mBAAmB,EAAE,YAEjC,CAAA;AAOD,eAAO,MAAM,qBAAqB,EAAE,YAAY,CAAC,YAAY,CAE3D,CAAA"}
1
+ {"version":3,"file":"AudioOptions.d.ts","sourceRoot":"","sources":["../../../../src/streaming/AudioOptions.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAQ5D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAE/C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;IAErD;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IACzB;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAA;IAEnC;;;;;;OAMG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAE1C;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IAEtC;;;;;;;;OAQG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CACzC;AAED,eAAO,MAAM,mBAAmB,EAAE,YAGjC,CAAA;AAOD,eAAO,MAAM,qBAAqB,EAAE,YAAY,CAAC,YAAY,CAK3D,CAAA"}
@@ -2,9 +2,10 @@
2
2
  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import { array, boolean, object, string } from '@amazon/vinyl-validation';
5
+ import { array, boolean, number, object, string, } from '@amazon/vinyl-validation';
6
6
  export const defaultAudioOptions = {
7
7
  selection: { language: null, descriptive: false },
8
+ disableSampleRateFilter: false,
8
9
  };
9
10
  const audioSelectionValidator = object({
10
11
  language: string().or(array(string()).readonly()).orNull().optional(),
@@ -12,4 +13,7 @@ const audioSelectionValidator = object({
12
13
  });
13
14
  export const audioOptionsValidator = object({
14
15
  selection: audioSelectionValidator.optional(),
16
+ disableSampleRateFilter: boolean().optional(),
17
+ maxSampleRate: number().maybe().optional(),
18
+ minSampleRate: number().maybe().optional(),
15
19
  });