Source: lib/media/presentation_timeline.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.media.PresentationTimeline');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.log');
  20. goog.require('shaka.media.SegmentReference');
  21. /**
  22. * Creates a PresentationTimeline.
  23. *
  24. * @param {?number} presentationStartTime The wall-clock time, in seconds,
  25. * when the presentation started or will start. Only required for live.
  26. * @param {number} presentationDelay The delay to give the presentation, in
  27. * seconds. Only required for live.
  28. *
  29. * @see {shakaExtern.Manifest}
  30. * @see {@tutorial architecture}
  31. *
  32. * @constructor
  33. * @struct
  34. * @export
  35. */
  36. shaka.media.PresentationTimeline = function(
  37. presentationStartTime, presentationDelay) {
  38. /** @private {?number} */
  39. this.presentationStartTime_ = presentationStartTime;
  40. /** @private {number} */
  41. this.presentationDelay_ = presentationDelay;
  42. /** @private {number} */
  43. this.duration_ = Infinity;
  44. /** @private {number} */
  45. this.segmentAvailabilityDuration_ = Infinity;
  46. /** @private {?number} */
  47. this.maxSegmentDuration_ = 1;
  48. /** @private {number} */
  49. this.clockOffset_ = 0;
  50. /** @private {boolean} */
  51. this.static_ = true;
  52. /** @private {number} */
  53. this.segmentAvailabilityStart_ = 0;
  54. };
  55. /**
  56. * @return {number} The presentation's duration in seconds.
  57. * Infinity indicates that the presentation continues indefinitely.
  58. * @export
  59. */
  60. shaka.media.PresentationTimeline.prototype.getDuration = function() {
  61. return this.duration_;
  62. };
  63. /**
  64. * Sets the presentation's duration.
  65. *
  66. * @param {number} duration The presentation's duration in seconds.
  67. * Infinity indicates that the presentation continues indefinitely.
  68. * @export
  69. */
  70. shaka.media.PresentationTimeline.prototype.setDuration = function(duration) {
  71. goog.asserts.assert(duration > 0, 'duration must be > 0');
  72. this.duration_ = duration;
  73. };
  74. /**
  75. * @return {?number} The presentation's start time in seconds.
  76. * @export
  77. */
  78. shaka.media.PresentationTimeline.prototype.getPresentationStartTime =
  79. function() {
  80. return this.presentationStartTime_;
  81. };
  82. /**
  83. * Sets the clock offset, which is the the difference between the client's clock
  84. * and the server's clock, in milliseconds (i.e., serverTime = Date.now() +
  85. * clockOffset).
  86. *
  87. * @param {number} offset The clock offset, in ms.
  88. * @export
  89. */
  90. shaka.media.PresentationTimeline.prototype.setClockOffset = function(offset) {
  91. this.clockOffset_ = offset;
  92. };
  93. /**
  94. * Sets the presentation's static flag.
  95. *
  96. * @param {boolean} isStatic If true, the presentation is static, meaning all
  97. * segments are available at once.
  98. * @export
  99. */
  100. shaka.media.PresentationTimeline.prototype.setStatic = function(isStatic) {
  101. // NOTE: the argument name is not "static" because that's a keyword in ES6
  102. this.static_ = isStatic;
  103. };
  104. /**
  105. * Gets the presentation's segment availability duration, which is the amount
  106. * of time, in seconds, that the start of a segment remains available after the
  107. * live-edge moves past the end of that segment. Infinity indicates that
  108. * segments remain available indefinitely. For example, if your live
  109. * presentation has a 5 minute DVR window and your segments are 10 seconds long
  110. * then the segment availability duration should be 4 minutes and 50 seconds.
  111. *
  112. * @return {number} The presentation's segment availability duration.
  113. * @export
  114. */
  115. shaka.media.PresentationTimeline.prototype.getSegmentAvailabilityDuration =
  116. function() {
  117. return this.segmentAvailabilityDuration_;
  118. };
  119. /**
  120. * Sets the presentation's segment availability duration. The segment
  121. * availability duration should only be set for live.
  122. *
  123. * @param {number} segmentAvailabilityDuration The presentation's new segment
  124. * availability duration in seconds.
  125. * @export
  126. */
  127. shaka.media.PresentationTimeline.prototype.setSegmentAvailabilityDuration =
  128. function(segmentAvailabilityDuration) {
  129. goog.asserts.assert(segmentAvailabilityDuration >= 0,
  130. 'segmentAvailabilityDuration must be >= 0');
  131. this.segmentAvailabilityDuration_ = segmentAvailabilityDuration;
  132. };
  133. /**
  134. * Sets the presentation delay.
  135. *
  136. * @param {number} delay
  137. * @export
  138. */
  139. shaka.media.PresentationTimeline.prototype.setDelay = function(delay) {
  140. goog.asserts.assert(delay >= 0, 'delay must be >= 0');
  141. this.presentationDelay_ = delay;
  142. };
  143. /**
  144. * Gives PresentationTimeline a Stream's segments so it can size and position
  145. * the segment availability window, and account for missing segment
  146. * information. This function should be called once for each Stream (no more,
  147. * no less).
  148. *
  149. * @param {number} periodStartTime
  150. * @param {!Array.<!shaka.media.SegmentReference>} references
  151. * @export
  152. */
  153. shaka.media.PresentationTimeline.prototype.notifySegments = function(
  154. periodStartTime, references) {
  155. if (references.length == 0)
  156. return;
  157. this.maxSegmentDuration_ = references.reduce(
  158. function(max, r) { return Math.max(max, r.endTime - r.startTime); },
  159. this.maxSegmentDuration_);
  160. shaka.log.v1('notifySegments:',
  161. 'maxSegmentDuration=' + this.maxSegmentDuration_);
  162. };
  163. /**
  164. * Gives PresentationTimeline a Stream's maximum segment duration so it can
  165. * size and position the segment availability window. This function should be
  166. * called once for each Stream (no more, no less), but does not have to be
  167. * called if notifySegments() is called instead for a particular stream.
  168. *
  169. * @param {number} maxSegmentDuration The maximum segment duration for a
  170. * particular stream.
  171. * @export
  172. */
  173. shaka.media.PresentationTimeline.prototype.notifyMaxSegmentDuration = function(
  174. maxSegmentDuration) {
  175. this.maxSegmentDuration_ = Math.max(
  176. this.maxSegmentDuration_, maxSegmentDuration);
  177. shaka.log.v1('notifyNewSegmentDuration:',
  178. 'maxSegmentDuration=' + this.maxSegmentDuration_);
  179. };
  180. /**
  181. * @return {boolean} True if the presentation is live; otherwise, return
  182. * false.
  183. * @export
  184. */
  185. shaka.media.PresentationTimeline.prototype.isLive = function() {
  186. return this.duration_ == Infinity &&
  187. !this.static_;
  188. };
  189. /**
  190. * @return {boolean} True if the presentation is in progress (meaning not live,
  191. * but also not completely available); otherwise, return false.
  192. * @export
  193. */
  194. shaka.media.PresentationTimeline.prototype.isInProgress = function() {
  195. return this.duration_ != Infinity &&
  196. !this.static_;
  197. };
  198. /**
  199. * Gets the presentation's current segment availability start time. Segments
  200. * ending at or before this time should be assumed to be unavailable.
  201. *
  202. * @return {number} The current segment availability start time, in seconds,
  203. * relative to the start of the presentation.
  204. * @export
  205. */
  206. shaka.media.PresentationTimeline.prototype.getSegmentAvailabilityStart =
  207. function() {
  208. return this.getSafeAvailabilityStart(0 /* delay */);
  209. };
  210. /**
  211. * Gets the presentation's current segment availability start time, offset by
  212. * the given amount. This is used to ensure that we don't "fall" back out of
  213. * the availability window while we are buffering.
  214. *
  215. * @param {number} offset The offset to add to the start time.
  216. * @return {number} The current segment availability start time, in seconds,
  217. * relative to the start of the presentation.
  218. * @export
  219. */
  220. shaka.media.PresentationTimeline.prototype.getSafeAvailabilityStart =
  221. function(offset) {
  222. if (this.segmentAvailabilityDuration_ == Infinity)
  223. return this.segmentAvailabilityStart_;
  224. var end = this.getSegmentAvailabilityEnd();
  225. var start = Math.min(end - this.segmentAvailabilityDuration_ + offset, end);
  226. return Math.max(this.segmentAvailabilityStart_, start);
  227. };
  228. /**
  229. * Sets the presentation's current segment availability start time.
  230. *
  231. * @param {number} time
  232. * @export
  233. */
  234. shaka.media.PresentationTimeline.prototype.setAvailabilityStart =
  235. function(time) {
  236. this.segmentAvailabilityStart_ = time;
  237. };
  238. /**
  239. * Gets the presentation's current segment availability end time. Segments
  240. * starting after this time should be assumed to be unavailable.
  241. *
  242. * @return {number} The current segment availability end time, in seconds,
  243. * relative to the start of the presentation. Always returns the
  244. * presentation's duration for video-on-demand.
  245. * @export
  246. */
  247. shaka.media.PresentationTimeline.prototype.getSegmentAvailabilityEnd =
  248. function() {
  249. if (!this.isLive() && !this.isInProgress())
  250. return this.duration_;
  251. return Math.min(this.getLiveEdge_(), this.duration_);
  252. };
  253. /**
  254. * Gets the seek range end.
  255. *
  256. * @return {number}
  257. * @export
  258. */
  259. shaka.media.PresentationTimeline.prototype.getSeekRangeEnd = function() {
  260. var useDelay = this.isLive() || this.isInProgress();
  261. var delay = useDelay ? this.presentationDelay_ : 0;
  262. return Math.max(0, this.getSegmentAvailabilityEnd() - delay);
  263. };
  264. /**
  265. * @return {number} The current presentation time in seconds.
  266. * @private
  267. */
  268. shaka.media.PresentationTimeline.prototype.getLiveEdge_ = function() {
  269. goog.asserts.assert(this.presentationStartTime_ != null,
  270. 'Cannot compute timeline live edge without start time');
  271. var now = (Date.now() + this.clockOffset_) / 1000.0;
  272. return Math.max(
  273. 0, now - this.maxSegmentDuration_ - this.presentationStartTime_);
  274. };
  275. if (!COMPILED) {
  276. /**
  277. * Debug only: assert that the timeline parameters make sense for the type of
  278. * presentation (VOD, IPR, live).
  279. */
  280. shaka.media.PresentationTimeline.prototype.assertIsValid = function() {
  281. if (this.isLive()) {
  282. // Implied by isLive(): infinite and dynamic.
  283. // Live streams should have a start time.
  284. goog.asserts.assert(this.presentationStartTime_ != null,
  285. 'Detected as live stream, but does not match our model of live!');
  286. } else if (this.isInProgress()) {
  287. // Implied by isInProgress(): finite and dynamic.
  288. // IPR streams should have a start time, and segments should not expire.
  289. goog.asserts.assert(this.presentationStartTime_ != null &&
  290. this.segmentAvailabilityDuration_ == Infinity,
  291. 'Detected as IPR stream, but does not match our model of IPR!');
  292. } else { // VOD
  293. // VOD segments should not expire and the presentation should be finite
  294. // and static.
  295. goog.asserts.assert(this.segmentAvailabilityDuration_ == Infinity &&
  296. this.duration_ != Infinity &&
  297. this.static_,
  298. 'Detected as VOD stream, but does not match our model of VOD!');
  299. }
  300. };
  301. }