/// <summary> /// In-thread handler for media file opening /// </summary> /// <param name="stream"></param> private void DoNextStream(EncryptedStream stream) { try { if (stream == null) { if (_state == State.Opening) RaiseError("unable to open stream"); if (_demux != null) _demux.Flush(true); _audioBuffer.EndOfPlayback = true; _videoBuffer.EndOfPlayback = true; ReportPendingSamples(); return; } UpdateDemux(stream); if (_state == State.Opening) { _isBuffering = true; Dictionary<MediaSourceAttributesKeys, string> sourceAttributes = new Dictionary<MediaSourceAttributesKeys, string>(); TimeSpan playlistDuration; if (_playback.IsEndList) { playlistDuration = _playback.Duration; sourceAttributes[MediaSourceAttributesKeys.CanSeek] = Boolean.TrueString; } else { if (_playback.Duration > _liveDvrMinDuration) { playlistDuration = _playback.Duration; sourceAttributes[MediaSourceAttributesKeys.CanSeek] = Boolean.TrueString; } else { playlistDuration = new TimeSpan(0, 0, 0); sourceAttributes[MediaSourceAttributesKeys.CanSeek] = Boolean.FalseString; } if (_playback.TargetDuration.TotalSeconds > 0 && _bwHistory.MaxHistoryCount < _playback.TargetDuration.TotalSeconds + 1) { _bwHistory.MaxHistoryCount = (int)_playback.TargetDuration.TotalSeconds + 1; } } if (null != _playlistOverrideEvent) { // Playlist is overrided use max timeline plus duration as whole duration sourceAttributes[MediaSourceAttributesKeys.Duration] = (playlistDuration + TimeSpan.FromTicks(_audioBuffer.MaxStartTimeInAllTimelines)).Ticks.ToString(); } else { sourceAttributes[MediaSourceAttributesKeys.Duration] = playlistDuration.Ticks.ToString(); } List<MediaStreamDescription> availableMediaStreams = new List<MediaStreamDescription>(); availableMediaStreams.Add(_audioBuffer.Description); // The video width and height that we pass to MediaElement should be set to the maximum resolution that // this HLS playlist could possibly use. This ensures that the codec can handle high resolution video // streams as well. The _videoBuffer.Description currently holds the resolution for the current playlist, // which is not necessairly the maximum resolution. If the resolution tag is missing for one of variants, // we will default to the maximim resolution of 1280x720. MediaStreamDescription videoMSD = _videoBuffer.Description; uint maxPicWidth = uint.Parse(videoMSD.MediaAttributes[MediaStreamAttributeKeys.Width]); uint maxPicHeight = uint.Parse(videoMSD.MediaAttributes[MediaStreamAttributeKeys.Height]); foreach (HLSVariant subPlaylist in this.Playback.Program.Variants) { string s; if (subPlaylist.MetaData != null && subPlaylist.MetaData.TryGetValue(HLSPlaylistMetaKeys.Resolution, out s)) { string[] components = s.Split(new char[] { 'x' }); if (components != null && components.Length == 2) { uint subStreamWidth = uint.Parse(components[0]); uint subStreamHeight = uint.Parse(components[1]); if (maxPicHeight < subStreamHeight) { maxPicHeight = subStreamHeight; } if (maxPicWidth < subStreamWidth) { maxPicWidth = subStreamWidth; } } } } if (_openParam.maxPicWidth > 0) { if (maxPicWidth < _openParam.maxPicWidth) { maxPicWidth = _openParam.maxPicWidth; } } if (_openParam.maxPicHeight > 0) { if (maxPicHeight < _openParam.maxPicHeight) { maxPicHeight = _openParam.maxPicHeight; } } videoMSD.MediaAttributes[MediaStreamAttributeKeys.Width] = maxPicWidth.ToString(); videoMSD.MediaAttributes[MediaStreamAttributeKeys.Height] = maxPicHeight.ToString(); availableMediaStreams.Add(videoMSD); ReportOpenMediaCompleted(sourceAttributes, availableMediaStreams); _state = State.Opened; } } catch (Exception exception) { RaiseError(exception); } finally { _playback.EndPendingWebRequest(); } }
/// <summary> /// Create new demux from a stream /// </summary> /// <param name="stream"></param> private void UpdateDemux(EncryptedStream stream) { TSDemux newDemux; if (_demux != null && _demux.IsMediaInfoReady ) { newDemux = new TSDemux(stream, _demux, _playback.Metadata, _playback.CurrentDownloadBitrate, _bwHistory); } else { newDemux = new TSDemux(stream, _audioBuffer, _videoBuffer, _playback.Metadata, _playback.CurrentDownloadBitrate, _bwHistory); } if (_demux != null) { if (!_demux.IsMediaInfoReady) { // If last demux has no media info ready, we are not carrying over demux parser buffers has been allocated, // so make sure we discard those buffers by Flush(true) _demux.Flush(true); HLSTrace.WriteLine("Discard samples as last demux has no media info ready"); } _demux.Dispose(); } _demux = newDemux; while (!_demux.IsMediaInfoReady && !_demux.IsEndOfStream) { _demux.ReadChunk(); } if (!_demux.IsMediaInfoReady) RaiseError("unable to parse stream"); }
/// <summary> /// Common construction code for all constructors /// </summary> /// <param name="stream"></param> /// <param name="audioBuffer"></param> /// <param name="videoBuffer"></param> private void CommonConstruct(EncryptedStream stream, SampleBuffer audioBuffer, SampleBuffer videoBuffer, IContainerMetadata metadata, uint bitrate, BandwidthHistory BWHistory) { if (stream == null) throw new ArgumentNullException("stream"); _stream = stream; _metadata = metadata; _audioBuffer = audioBuffer; _audioBuffer.ResetOnSegmentStart(); _videoBuffer = videoBuffer; _videoBuffer.ResetOnSegmentStart(); _previousReadEndTime = stream.RequestStartTime; _bitrate = bitrate; _downloadChunkBuffer = new byte[_downloadChunkSize]; _TSPacketBuffer = new byte[TSPacketSize]; _BWHistory = BWHistory; }
/// <summary> /// Implements Dispose logic /// </summary> /// <param name="disposing"></param> protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { if (_stream != null) { _stream.Dispose(); _stream = null; } } _disposed = true; } }
/// <summary> /// Special constructor for creating new demux for next stream in sequence /// </summary> /// <param name="stream"></param> /// <param name="previousDemux"></param> public TSDemux(EncryptedStream stream, TSDemux previousDemux, IContainerMetadata metadata, uint bitrate, BandwidthHistory BWHistory) { if (previousDemux == null) throw new ArgumentNullException("previousDemux"); CommonConstruct(stream, previousDemux._audioBuffer, previousDemux._videoBuffer, metadata, bitrate, BWHistory); if (!stream.Discontinuity) _streams = previousDemux._streams; _audioFormatParser = previousDemux._audioFormatParser; _videoFormatParser = previousDemux._videoFormatParser; _audioFormatParser.HLSStream = stream.HLSStream; _videoFormatParser.HLSStream = stream.HLSStream; }
/// <summary> /// Default constructor. /// </summary> /// <param name="stream"></param> /// <param name="audioBuffer"></param> /// <param name="videoBuffer"></param> public TSDemux(EncryptedStream stream, SampleBuffer audioBuffer, SampleBuffer videoBuffer, IContainerMetadata metadata, uint bitrate, BandwidthHistory BWHistory) { CommonConstruct(stream, audioBuffer, videoBuffer, metadata, bitrate, BWHistory); }
/// <summary> /// Asynchronous callback for handling stream data /// </summary> /// <param name="asyncResult"></param> private void DataStreamResponseReceived(IAsyncResult asyncResult) { lock (_requestLock) { WebRequestState requestState = null; HttpWebResponse response = null; try { Debug.Assert(asyncResult.AsyncState is WebRequestState, "asyncResult.AsyncState must be of type WebRequestState"); requestState = (WebRequestState)(asyncResult.AsyncState); if (requestState.DiscardData) { return; } response = (HttpWebResponse)requestState.WebRequest.EndGetResponse(asyncResult); HLSTrace.TestInjectRandomError( "DataStreamResponseReceived", 0.1f ); HLSTrace.WriteLine("Downloaded response status {0} for {1}", response.StatusDescription, requestState.WebRequest.RequestUri.ToString()); if (requestState.DiscardData || requestState.AsyncResult.IsAborted) { requestState.AsyncResult.Dispose(); if (response.GetResponseStream() != null) response.GetResponseStream().Close(); response.Close(); response = null; return; } if (response.GetResponseStream() != null) { EncryptedStream playbackStream = null; bool discontinuity = _discontinuity || CurrentStream.Discontinuity; if (CurrentStream.EncryptionMethod == HLSEncryptionMethod.AES128) { using (AesManaged aes = new AesManaged()) { aes.Key = _playlist.GetEncryptionKeyForStream(CurrentStream); aes.IV = SynthesizeInitializationVector(); playbackStream = new EncryptedStream(response.GetResponseStream(), requestState.StartTime, response, discontinuity, aes.CreateDecryptor(), TSDemux.TSPacketSize, CurrentStream); } } else { playbackStream = new EncryptedStream(response.GetResponseStream(), requestState.StartTime, response, discontinuity, CurrentStream); } Debug.Assert(_asyncResult != null, "_asyncResult cannot be null"); _asyncResult.CompleteWithAsyncState(playbackStream); } else { _asyncResult.CompleteWithAsyncState(null); } } catch (Exception e) { HLSTrace.PrintException(e); if (response != null) { response.Close(); } if (requestState.DiscardData || requestState.AsyncResult.IsAborted) { requestState.AsyncResult.Dispose(); return; } if (_reTryCount < MAX_NUMBER_OF_RETRIES) { HLSTrace.WriteLine( " download retrying... count={0}", _reTryCount ); BeginLoadingNextStream(); _reTryCount++; } else { lock (this) { HLSTrace.WriteLine(" download retries all failed, skip it and download next chunk ... count={0}", _reTryCount); _reTryCount = 0; _currentPosition++; _currentMediaSequenceNumber++; _discontinuity = true; if (!IsEndList && (_currentPosition >= _playlist.Streams.Count)) { _asyncResult.Purpose = HLSCallbackPurpose.WaitForPlaylist; _playlist.Reload(); return; } BeginLoadingNextStream(); } } } } }