Source: lib/text/simple_text_displayer.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.text.SimpleTextDisplayer');
  18. goog.require('shaka.log');
  19. /**
  20. * <p>
  21. * This defines the default text displayer plugin. An instance of this
  22. * class is used when no custom displayer is given.
  23. * </p>
  24. * <p>
  25. * This class simply converts shaka.text.Cue objects to
  26. * TextTrackCues and feeds them to the browser.
  27. * </p>
  28. *
  29. * @param {HTMLMediaElement} video
  30. * @constructor
  31. * @struct
  32. * @implements {shakaExtern.TextDisplayer}
  33. * @export
  34. */
  35. shaka.text.SimpleTextDisplayer = function(video) {
  36. /** @private {TextTrack} */
  37. this.textTrack_ = null;
  38. // TODO: test that in all cases, the built-in CC controls in the video element
  39. // are toggling our TextTrack.
  40. // If the video element has TextTracks, disable them. If we see one that
  41. // was created by a previous instance of Shaka Player, reuse it.
  42. for (var i = 0; i < video.textTracks.length; ++i) {
  43. var track = video.textTracks[i];
  44. track.mode = 'disabled';
  45. if (track.label == shaka.text.SimpleTextDisplayer.TextTrackLabel_) {
  46. this.textTrack_ = track;
  47. }
  48. }
  49. if (!this.textTrack_) {
  50. // As far as I can tell, there is no observable difference between setting
  51. // kind to 'subtitles' or 'captions' when creating the TextTrack object.
  52. // The individual text tracks from the manifest will still have their own
  53. // kinds which can be displayed in the app's UI.
  54. this.textTrack_ = video.addTextTrack(
  55. 'subtitles', shaka.text.SimpleTextDisplayer.TextTrackLabel_);
  56. }
  57. this.textTrack_.mode = 'hidden';
  58. };
  59. /**
  60. * @override
  61. * @export
  62. */
  63. shaka.text.SimpleTextDisplayer.prototype.remove = function(start, end) {
  64. // Check that the displayer hasn't been destroyed.
  65. if (!this.textTrack_) return false;
  66. this.removeWhere_(function(cue) {
  67. if (cue.startTime >= end || cue.endTime <= start) {
  68. // Outside the remove range. Hang on to it.
  69. return false;
  70. }
  71. return true;
  72. });
  73. return true;
  74. };
  75. /**
  76. * @override
  77. * @export
  78. */
  79. shaka.text.SimpleTextDisplayer.prototype.append = function(cues) {
  80. var textTrackCues = [];
  81. for (var i = 0; i < cues.length; i++) {
  82. var cue = this.convertToTextTrackCue_(cues[i]);
  83. if (cue)
  84. textTrackCues.push(cue);
  85. }
  86. // Sort the cues based on start/end times. Make a copy of the array so
  87. // we can get the index in the original ordering. Out of order cues are
  88. // rejected by IE/Edge. See https://goo.gl/BirBy9
  89. var sortedCues = textTrackCues.slice().sort(function(a, b) {
  90. if (a.startTime != b.startTime)
  91. return a.startTime - b.startTime;
  92. else if (a.endTime != b.endTime)
  93. return a.endTime - b.startTime;
  94. else
  95. // The browser will display cues with identical time ranges from the
  96. // bottom up. Reversing the order of equal cues means the first one
  97. // parsed will be at the top, as you would expect.
  98. // See https://github.com/google/shaka-player/issues/848 for more
  99. // information.
  100. return textTrackCues.indexOf(b) - textTrackCues.indexOf(a);
  101. });
  102. sortedCues.forEach(function(cue) {
  103. this.textTrack_.addCue(cue);
  104. }.bind(this));
  105. };
  106. /**
  107. * @override
  108. * @export
  109. */
  110. shaka.text.SimpleTextDisplayer.prototype.destroy = function() {
  111. if (this.textTrack_) {
  112. this.removeWhere_(function(cue) { return true; });
  113. }
  114. this.textTrack_ = null;
  115. return Promise.resolve();
  116. };
  117. /**
  118. * @override
  119. * @export
  120. */
  121. shaka.text.SimpleTextDisplayer.prototype.isTextVisible = function() {
  122. return this.textTrack_.mode == 'showing';
  123. };
  124. /**
  125. * @override
  126. * @export
  127. */
  128. shaka.text.SimpleTextDisplayer.prototype.setTextVisibility = function(on) {
  129. this.textTrack_.mode = on ? 'showing' : 'hidden';
  130. };
  131. /**
  132. * @param {!shaka.text.Cue} shakaCue
  133. * @return {TextTrackCue}
  134. * @private
  135. */
  136. shaka.text.SimpleTextDisplayer.prototype.convertToTextTrackCue_ =
  137. function(shakaCue) {
  138. if (shakaCue.startTime >= shakaCue.endTime) {
  139. // IE/Edge will throw in this case.
  140. // See issue #501
  141. shaka.log.warning('Invalid cue times: ' + shakaCue.startTime +
  142. ' - ' + shakaCue.endTime);
  143. return null;
  144. }
  145. var Cue = shaka.text.Cue;
  146. var vttCue = new VTTCue(shakaCue.startTime,
  147. shakaCue.endTime,
  148. shakaCue.payload);
  149. // NOTE: positionAlign and lineAlign settings are not supported by Chrome
  150. // at the moment, so setting them will have no effect.
  151. // The bug on chromium to implement them:
  152. // https://bugs.chromium.org/p/chromium/issues/detail?id=633690
  153. vttCue.lineAlign = shakaCue.lineAlign;
  154. vttCue.positionAlign = shakaCue.positionAlign;
  155. vttCue.size = shakaCue.size;
  156. try {
  157. // Safari 10 seems to throw on align='center'.
  158. // Catch any exception and let the workaround below take care of it.
  159. vttCue.align = shakaCue.textAlign;
  160. } catch (exception) {}
  161. if (shakaCue.textAlign == 'center' && vttCue.align != 'center') {
  162. // Workaround for a Chrome bug http://crbug.com/663797
  163. // Chrome does not support align = 'center'
  164. vttCue.position = 'auto';
  165. vttCue.align = 'middle';
  166. }
  167. if (shakaCue.writingDirection == Cue.writingDirection.VERTICAL_LEFT_TO_RIGHT)
  168. vttCue.vertical = 'lr';
  169. else if (shakaCue.writingDirection ==
  170. Cue.writingDirection.VERTICAL_RIGHT_TO_LEFT)
  171. vttCue.vertical = 'rl';
  172. // snapToLines flag is true by default
  173. if (shakaCue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE)
  174. vttCue.snapToLines = false;
  175. if (shakaCue.line != null)
  176. vttCue.line = shakaCue.line;
  177. if (shakaCue.position != null)
  178. vttCue.position = shakaCue.position;
  179. return vttCue;
  180. };
  181. /**
  182. * Remove all cues for which the matching function returns true.
  183. *
  184. * @param {function(!TextTrackCue):boolean} predicate
  185. * @private
  186. */
  187. shaka.text.SimpleTextDisplayer.prototype.removeWhere_ = function(predicate) {
  188. var cues = this.textTrack_.cues;
  189. var removeMe = [];
  190. // Remove these in another loop to avoid mutating the TextTrackCueList
  191. // while iterating over it. This allows us to avoid making assumptions
  192. // about whether or not this.textTrack_.remove() will alter that list.
  193. for (var i = 0; i < cues.length; ++i) {
  194. if (predicate(cues[i])) {
  195. removeMe.push(cues[i]);
  196. }
  197. }
  198. for (var i = 0; i < removeMe.length; ++i) {
  199. this.textTrack_.removeCue(removeMe[i]);
  200. }
  201. };
  202. /**
  203. * @const {string}
  204. * @private
  205. */
  206. shaka.text.SimpleTextDisplayer.TextTrackLabel_ = 'Shaka Player TextTrack';