Source: media/text_stream.js

/**
 * @license
 * Copyright 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.media.TextStream');

goog.require('shaka.log');
goog.require('shaka.media.IStream');
goog.require('shaka.media.SegmentIndex');
goog.require('shaka.media.StreamInfo');
goog.require('shaka.util.FakeEventTarget');
goog.require('shaka.util.PublicPromise');



/**
 * Creates a TextStream, which presents subtitles via an HTMLVideoElement's
 * built-in subtitles support.
 *
 * @param {!shaka.util.FakeEventTarget} parent The parent for event bubbling.
 * @param {!HTMLVideoElement} video The video element.
 *
 * @fires shaka.media.IStream.AdaptationEvent
 *
 * @struct
 * @constructor
 * @implements {shaka.media.IStream}
 * @extends {shaka.util.FakeEventTarget}
 */
shaka.media.TextStream = function(parent, video) {
  shaka.util.FakeEventTarget.call(this, parent);

  /** @private {!HTMLVideoElement} */
  this.video_ = video;

  /** @private {boolean} */
  this.enabled_ = true;

  /** @private {shaka.media.StreamInfo} */
  this.streamInfo_ = null;

  /** @private {shaka.media.SegmentIndex} */
  this.segmentIndex_ = null;

  /** @private {!shaka.util.PublicPromise} */
  this.startedPromise_ = new shaka.util.PublicPromise();

  /** @private {HTMLTrackElement} */
  this.track_ = null;
};
goog.inherits(shaka.media.TextStream, shaka.util.FakeEventTarget);


/** @override */
shaka.media.TextStream.prototype.configure = function(config) {};


/**
 * @override
 * @suppress {checkTypes} to set otherwise non-nullable types to null.
 */
shaka.media.TextStream.prototype.destroy = function() {
  if (this.track_) {
    this.video_.removeChild(this.track_);
  }

  this.startedPromise_.destroy();
  this.startedPromise_ = null;
  this.track_ = null;
  this.segmentIndex_ = null;
  this.streamInfo_ = null;
  this.video_ = null;
  this.parent = null;
};


/** @override */
shaka.media.TextStream.prototype.getStreamInfo = function() {
  return this.streamInfo_;
};


/** @override */
shaka.media.TextStream.prototype.getSegmentIndex = function() {
  return this.segmentIndex_;
};


/**
 * Returns a Promise that the Stream will resolve after it has begun presenting
 * its first text stream. The Stream will never reject the returned Promise.
 *
 * @override
 */
shaka.media.TextStream.prototype.started = function(proceed) {
  return this.startedPromise_;
};


/**
 * Always returns true since text streams are not segmented.
 *
 * @override
 */
shaka.media.TextStream.prototype.hasEnded = function() {
  return true;
};


/**
 * Starts presenting the specified stream asynchronously.
 * Note: |clearBuffer| and |opt_clearBufferOffset| are ignored.
 *
 * @override
 */
shaka.media.TextStream.prototype.switch = function(
    streamInfo, clearBuffer, opt_clearBufferOffset) {
  shaka.log.info('Switching stream to', streamInfo);

  streamInfo.segmentIndexSource.create().then(shaka.util.TypedBind(this,
      /** @param {!shaka.media.SegmentIndex} segmentIndex */
      function(segmentIndex) {
        if (!this.video_) {
          // We got destroyed.
          return;
        }

        if (segmentIndex.length() == 0) {
          return Promise.reject(new Error('No subtitles URL available.'));
        }

        var previousStreamInfo = this.streamInfo_;

        this.streamInfo_ = streamInfo;
        this.segmentIndex_ = segmentIndex;

        // TODO: Add support for failover in subtitles?
        var subtitlesUrl = segmentIndex.first().url.urls[0].toString();

        // Save the enabled flag so that changing the active text track does
        // not change the visibility of the text track.
        var enabled = this.getEnabled();

        // NOTE: Simply changing the src attribute of an existing track may
        // result in both the old and new subtitles appearing simultaneously.
        // To be safe, remove the old track and create a new one.
        if (this.track_) {
          // NOTE: When the current track is enabled, and we change tracks and
          // immediately disable the new one, the new one seems to end up
          // enabled anyway.  To solve this, we disable the current track
          // before removing.
          this.setEnabled(false);
          this.video_.removeChild(this.track_);
        }

        this.track_ = /** @type {HTMLTrackElement} */
            (document.createElement('track'));
        this.video_.appendChild(this.track_);

        this.track_.src = subtitlesUrl;

        // NOTE: mode must be set after appending to the DOM.
        this.setEnabled(enabled);

        var event = shaka.media.TextStream.createAdaptationEvent_(streamInfo);
        this.dispatchEvent(event);

        if (!previousStreamInfo) {
          this.startedPromise_.resolve(0 /* timestampCorrection */);
        }
      }));
};


/**
 * Does nothing since text streams do not require manual resynchronization.
 *
 * @override
 */
shaka.media.TextStream.prototype.resync = function() {};


/**
 * Returns true since text streams don't need to buffer.
 *
 * @override
 */
shaka.media.TextStream.prototype.isBuffered = function(time) {
  return true;
};


/** @override */
shaka.media.TextStream.prototype.setEnabled = function(enabled) {
  this.enabled_ = enabled;
  if (this.track_) {
    this.track_.track.mode = enabled ? 'showing' : 'disabled';
  }
};


/** @override */
shaka.media.TextStream.prototype.getEnabled = function() {
  if (this.track_) {
    // Track mode can be updated by the video element using built-in controls.
    this.enabled_ = this.track_.track.mode == 'showing';
  }
  return this.enabled_;
};


/**
 * Creates an event object for an AdaptationEvent using the given StreamInfo.
 *
 * @param {!shaka.media.StreamInfo} streamInfo
 * @return {!Event}
 * @private
 */
shaka.media.TextStream.createAdaptationEvent_ = function(streamInfo) {
  var event = shaka.util.FakeEvent.create({
    'type': 'adaptation',
    'bubbles': true,
    'contentType': 'text',
    'size': null,
    'bandwidth': streamInfo.bandwidth
  });
  return event;
};