UNPKG

shaka-player

Version:
343 lines (276 loc) 8.89 kB
/** * @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.SimpleAbrManager'); goog.require('shaka.asserts'); goog.require('shaka.log'); goog.require('shaka.media.IAbrManager'); goog.require('shaka.player.AudioTrack'); goog.require('shaka.player.IVideoSource'); goog.require('shaka.player.VideoTrack'); goog.require('shaka.util.EventManager'); goog.require('shaka.util.IBandwidthEstimator'); /** * Creates a SimpleAbrManager, which selects video tracks using a basic set of * heuristics. * * @struct * @constructor * @implements {shaka.media.IAbrManager} * @export */ shaka.media.SimpleAbrManager = function() { /** @private {shaka.util.IBandwidthEstimator} */ this.estimator_ = null; /** @private {shaka.player.IVideoSource} */ this.videoSource_ = null; /** @private {!shaka.util.EventManager} */ this.eventManager_ = new shaka.util.EventManager(); /** * The timestamp after which we are allowed to adapt, in milliseconds. * @private {number} */ this.nextAdaptationTime_ = Number.POSITIVE_INFINITY; /** @private {boolean} */ this.enabled_ = true; /** @private {boolean} */ this.started_ = false; }; /** * The minimum amount of time that must pass before the first switch, in * milliseconds. This gives the bandwidth estimator time to get some real * data before changing anything. * * @private * @const {number} */ shaka.media.SimpleAbrManager.FIRST_SWITCH_INTERVAL_ = 4000; /** * The minimum amount of time that must pass between switches, in milliseconds. * This keeps us from changing too often and annoying the user. * * @private * @const {number} */ shaka.media.SimpleAbrManager.MIN_SWITCH_INTERVAL_ = 30000; /** * The minimum amount of time that must pass between bandwidth evaluations, in * milliseconds. This keeps us from checking for adaptation opportunities too * often. * * @private * @const {number} */ shaka.media.SimpleAbrManager.MIN_EVAL_INTERVAL_ = 3000; /** * The fraction of the estimated bandwidth which we should try to use when * upgrading. * * @private * @const {number} */ shaka.media.SimpleAbrManager.BANDWIDTH_UPGRADE_TARGET_ = 0.85; /** * The fraction of the estimated bandwidth we should downgrade to avoid * exceeding. * * @private * @const {number} */ shaka.media.SimpleAbrManager.BANDWIDTH_DOWNGRADE_TARGET_ = 0.95; /** * @override * @suppress {checkTypes} to set otherwise non-nullable types to null. */ shaka.media.SimpleAbrManager.prototype.destroy = function() { this.eventManager_.destroy(); this.eventManager_ = null; this.estimator_ = null; this.videoSource_ = null; }; /** @override */ shaka.media.SimpleAbrManager.prototype.initialize = function( estimator, videoSource) { if (this.estimator_ || this.videoSource_) { return; } this.estimator_ = estimator; this.videoSource_ = videoSource; }; /** @override */ shaka.media.SimpleAbrManager.prototype.start = function() { if (!this.estimator_ || !this.videoSource_ || this.started_) { return; } this.nextAdaptationTime_ = Date.now() + shaka.media.SimpleAbrManager.FIRST_SWITCH_INTERVAL_; this.eventManager_.listen(this.estimator_, 'bandwidth', this.onBandwidth_.bind(this)); this.eventManager_.listen(this.videoSource_, 'adaptation', this.onAdaptation_.bind(this)); this.eventManager_.listen(this.videoSource_, 'trackschanged', this.chooseNewTrack_.bind(this)); this.started_ = true; }; /** @override */ shaka.media.SimpleAbrManager.prototype.enable = function(enabled) { this.enabled_ = enabled; }; /** @override */ shaka.media.SimpleAbrManager.prototype.getInitialVideoTrackId = function() { if (!this.estimator_ || !this.videoSource_) { return null; } var chosen = this.chooseVideoTrack_(); return chosen ? chosen.id : null; }; /** * Select the specified video track. * * Does nothing if the AbrManager has not been started. * * @param {shaka.player.VideoTrack} track the track to the switch to * @param {boolean} clearBuffer If true, removes the previous stream's content * before switching to the new stream. * @param {number=} opt_clearBufferOffset if |clearBuffer| and * |opt_clearBufferOffset| are truthy, clear the stream buffer from the * given offset (relative to the video's current time) to the end of the * stream. * * @protected * @expose */ shaka.media.SimpleAbrManager.prototype.selectVideoTrack = function( track, clearBuffer, opt_clearBufferOffset) { if (!this.started_) { return; } this.videoSource_.selectVideoTrack( track.id, clearBuffer, opt_clearBufferOffset); }; /** * Find the active track in the list. * * @param {!Array.<T>} trackList * @return {T} * * @template T * @private */ shaka.media.SimpleAbrManager.findActiveTrack_ = function(trackList) { for (var i = 0; i < trackList.length; ++i) { if (trackList[i].active) { return trackList[i]; } } return null; }; /** * Handles bandwidth update events. * * @param {!Event} event * @private */ shaka.media.SimpleAbrManager.prototype.onBandwidth_ = function(event) { if (Date.now() < this.nextAdaptationTime_) { return; } this.chooseNewTrack_(event); }; /** * Makes adaptation decisions. * * @param {!Event} event * @private */ shaka.media.SimpleAbrManager.prototype.chooseNewTrack_ = function(event) { if (!this.enabled_) { return; } // Alias. var SimpleAbrManager = shaka.media.SimpleAbrManager; var chosen = this.chooseVideoTrack_(); if (chosen) { if (chosen.active) { // We are already using the correct video track. this.nextAdaptationTime_ = Date.now() + SimpleAbrManager.MIN_EVAL_INTERVAL_; return; } shaka.log.info('Video adaptation:', chosen); this.selectVideoTrack(chosen, false); } // Can't adapt again until we get confirmation of this one. this.nextAdaptationTime_ = Number.POSITIVE_INFINITY; }; /** * Handles adaptation events. * * @param {!Event} event * @private */ shaka.media.SimpleAbrManager.prototype.onAdaptation_ = function(event) { // This check allows us to ignore the initial adaptation events, which would // otherwise cause us not to honor FIRST_SWITCH_INTERVAL_. if (this.nextAdaptationTime_ == Number.POSITIVE_INFINITY) { // Adaptation is complete, so schedule the next adaptation. this.nextAdaptationTime_ = Date.now() + shaka.media.SimpleAbrManager.MIN_SWITCH_INTERVAL_; } }; /** * Choose a video track based on current bandwidth conditions. * * @return {shaka.player.VideoTrack} The chosen video track or null if there * are no video tracks to choose. * @private */ shaka.media.SimpleAbrManager.prototype.chooseVideoTrack_ = function() { // Alias. var SimpleAbrManager = shaka.media.SimpleAbrManager; var videoTracks = this.videoSource_.getVideoTracks(); if (videoTracks.length == 0) { return null; } videoTracks.sort(shaka.player.VideoTrack.compare); var activeAudioTrack = SimpleAbrManager.findActiveTrack_(this.videoSource_.getAudioTracks()); var audioBandwidth = activeAudioTrack ? activeAudioTrack.bandwidth : 0; var bandwidth = this.estimator_.getBandwidth(); // Start by assuming that we will use the first track. var chosen = videoTracks[0]; for (var i = 0; i < videoTracks.length; ++i) { var track = videoTracks[i]; var nextTrack = (i + 1 < videoTracks.length) ? videoTracks[i + 1] : { bandwidth: Number.POSITIVE_INFINITY }; // Ignore any track which is missing bandwidth info. if (!track.bandwidth) continue; var minBandwidth = (track.bandwidth + audioBandwidth) / SimpleAbrManager.BANDWIDTH_DOWNGRADE_TARGET_; var maxBandwidth = (nextTrack.bandwidth + audioBandwidth) / SimpleAbrManager.BANDWIDTH_UPGRADE_TARGET_; shaka.log.v2('Bandwidth ranges:', ((track.bandwidth + audioBandwidth) / 1e6).toFixed(3), (minBandwidth / 1e6).toFixed(3), (maxBandwidth / 1e6).toFixed(3)); if (bandwidth >= minBandwidth && bandwidth <= maxBandwidth) { chosen = track; if (chosen.active) break; } } return chosen; };