/// <summary> /// heuristics implementation to select default variant /// </summary> /// void IVariantSelector.SelectVariant(HLSVariant previousVariant, HLSVariant heuristicSuggestedVariant, ref HLSVariant nextVariant, List<HLSVariant> availableVariants) { double avgBandwidth = _bwHistory.GetAverageBandwidth(); double latestBandwidth = _bwHistory.GetLatestBandwidth(); int i; if (_lastSeekPlaylistPosition == _playback.CurrentPosition) { // it is the first segment after seek. use a ratio of current bandwidth to speed up start time. avgBandwidth = (int)( (float)(avgBandwidth) * _bandwidthUsageRatioForFirstSegment ); } // intialize with lowest bitrate playlist nextVariant = availableVariants[0]; for (i = 0; i < availableVariants.Count; i++) { if (availableVariants[i].Bitrate >= _minBitrate) { nextVariant = availableVariants[i]; break; } } // This implements the logic for selecting next variant to download. // We go over the variants, in the order of the highest bitrate to lowest bitrate, and calculate the targtet buffer // after _lookforwardDuration from current time, and select the first variant that meet these two condition: // 1) the end buffer size after _lookforwardDuration is larger than _highHeuristicBufferThreshold // 2) the low buffer (the smallest buffer size during the looking forward scan) is higher than _lowHeuristicBufferThreshold or current buffer size for (i = availableVariants.Count - 1; i >= 0; i--) { if (availableVariants[i].Bitrate >= _minBitrate && availableVariants[i].Bitrate <= _maxBitrate) { TimeSpan targetBufferSize; TimeSpan lowBuffer; CalculateTargeBuffer(availableVariants[i], avgBandwidth, out targetBufferSize, out lowBuffer); if ((lowBuffer > _lowHeuristicBufferThreshold || lowBuffer >= BufferLevel) && targetBufferSize > _highHeuristicBufferThreshold) { nextVariant = availableVariants[i]; break; } } } }
/// <summary> /// calculate target buffer size for a given bandwidth and HLS variant. /// </summary> private void CalculateTargeBuffer(HLSVariant nextVariant, double bandwidth, out TimeSpan targetBuffer, out TimeSpan lowbuffer) { TimeSpan totalDuration = TimeSpan.FromTicks(0); int streamIndex = _playback.CurrentPosition; if (bandwidth == BandwidthHistory.UnknownBandwidth || bandwidth == 0.00) { targetBuffer = TimeSpan.FromTicks(0); lowbuffer = TimeSpan.FromTicks(0); return; } TimeSpan SegmentDefaultDuration = TimeSpan.FromTicks(0); if (nextVariant.IsLoaded) { // if we cannot get the segment duration from stream, therefore we will use the target // duration tag instead. The target duration tag is mandatory according to HLS standard; // however, it is yet missing from some HLS sources. If target duration tag is missing, // we print out a warning message, and then we attempt to use the last segment duration, // and if that fails, we use a default duration _defaultTargetDuration (10 seconds). If // the actual segments have a different duration, then this will result in wrong // heuristics stream selection. try { SegmentDefaultDuration = nextVariant.TargetDuration; } catch (HLSPlaylistException e) { HLSTrace.PrintException(e); if (!_targetDurationWarningShown) { Debug.WriteLine("The mandatory EXT-X-TARGETDURATION is missing from the HLS playlist. \r\n" + "The heuristics algorithm needs this tag for its buffer calculations. \r\n" + "This may result in errors in heuristics calculations. \r\n"); _targetDurationWarningShown = true; } if (nextVariant.Streams != null && nextVariant.Streams.Count != 0) SegmentDefaultDuration = nextVariant.Streams[nextVariant.Streams.Count - 1].Duration; else SegmentDefaultDuration = _defaultTargetDuration; } } targetBuffer = BufferLevel; lowbuffer = BufferLevel; while (totalDuration < _lookforwardDuration) { double segmentSize = 0.00; TimeSpan segmentPlaybackDuration = TimeSpan.FromTicks(0); if (nextVariant.Streams == null || streamIndex >= nextVariant.Streams.Count) { segmentPlaybackDuration = SegmentDefaultDuration; } else { segmentPlaybackDuration = nextVariant.Streams[streamIndex].Duration; segmentSize = nextVariant.Streams[streamIndex].Size; streamIndex++; } // if duration is missing, use the default if (segmentPlaybackDuration.TotalMilliseconds == 0.00) segmentPlaybackDuration = _defaultTargetDuration; // if segment size is not specified in playlist, use duration * bitrate if (segmentSize == 0) segmentSize = ((double)nextVariant.Bitrate * segmentPlaybackDuration.TotalSeconds) / 8.00; TimeSpan segmentDownloadDuration = TimeSpan.FromSeconds((segmentSize * 8.00) / bandwidth); targetBuffer += ( segmentPlaybackDuration - segmentDownloadDuration ); totalDuration += segmentPlaybackDuration; if (targetBuffer < lowbuffer) lowbuffer = targetBuffer; } }