/// <summary> /// Clear the queue and add 1 item in the same operation. This is useful /// for operation that take precedence over all others (like closing and errors) /// </summary> /// <param name="elem">New item to add</param> public void ClearAndEnqueue(WorkQueueElement elem) { lock (m_queue) { m_queue.Clear(); m_queue.Enqueue(elem); m_queueHasItemsEvent.Set(); } }
/// <summary> /// Handle a sample request /// </summary> /// <param name="elem">the work command we are handling</param> private void DoSample(WorkQueueElement elem) { DateTime enter = DateTime.Now; // Give the sample if available, nothing otherwise MediaStreamType mediaStreamType = (MediaStreamType)elem.CommandParameter; int mediaTypeIndex = (int)mediaStreamType; // Get the queue of chunks for this stream MediaChunkQueue mediaQueue = m_manifestInfo.GetStreamInfoForStreamType(mediaStreamType).Queue; int mediaTypeMask = mediaStreamType == MediaStreamType.Video ? 0x01 : 0x02; MediaChunk chunk = null; GetFrameData frameData = null; bool flag = true; while (flag) { // Get the current chunk from our media queue flag = false; chunk = mediaQueue.Current; if (chunk == null) { // This is a redudant code in a case native side calls twice after the end of media // It should not happen but because we played with serialization on/off we keep it in place as a defensive code DoReportGetSampleCompleted(null, new MediaStreamSample(m_manifestInfo.GetStreamInfoForStreamType(mediaStreamType).Description, null, 0, 0, 0, m_nullAttributes)); return; } // If we have not finished parsing the chunk yet, then we can't send the sample if (chunk.State == MediaChunk.ChunkState.Pending || chunk.State == MediaChunk.ChunkState.Loaded) { // If the chunk is pending but hasn't been downloaded yet, then force a download if (chunk.State == MediaChunk.ChunkState.Pending && chunk.Downloader == null) { if (chunk.Bitrate == 0) { // Silverlight failed to load previous chunk or inform us about failure and as a result we did not even started a new one chunk.Bitrate = m_manifestInfo.GetStreamInfoForStreamType(chunk.MediaType).Bitrates[0]; } Tracer.Trace(TraceChannel.Error, "Lost {0} in state {1} trying to load again.", chunk.Sid, chunk.State); m_heuristics.ForceNextDownload(chunk.StreamId, chunk.ChunkId); m_heuristics.ScheduleDownloads(); } // Media chunk is not yet available, try again later m_workQueue.Enqueue(elem); ReportGetSampleProgress(chunk.DownloadPercent / 100.0f); if ((m_bufferingStateMask & mediaTypeMask) == 0) { if (m_bufferingStateMask == 0) { FireBufferingStarted(); } m_bufferingStateMask |= mediaTypeMask; } // Take a nap to give us some time to download. If it's already downloaded, // then it just hasn't been parsed yet if (chunk.DownloadPercent < 100) { Thread.Sleep(10); } if (chunk.SampleRequestsMissed++ == 0) { Tracer.Trace( TraceChannel.Error, "Chunk {0} is not available on sample request, chunk state {1}, downloader is {2}", chunk.Sid, chunk.State, chunk.Downloader == null ? "null" : "not null"); } else if (chunk.SampleRequestsMissed % 100 == 0) { Tracer.Trace( TraceChannel.Error, "Chunk {0} is not available for {3} seconds, chunk state {1}, downloader is {2}", chunk.Sid, chunk.State, chunk.Downloader == null ? "null" : "not null", chunk.SampleRequestsMissed / 100); } else if (chunk.SampleRequestsMissed >= (m_playbackInfo.IsPlaying ? 500 : 1500)) { // After 5 seconds delay during play or 15 seconds while paused or stopped, move on to the next chunk. if (chunk.Downloader != null) { chunk.Downloader.CancelDownload(); } chunk.SampleRequestsMissed = 0; m_consecutiveMissedChunks[mediaTypeIndex]++; string msg = String.Format(CultureInfo.InvariantCulture, "Failed to load in time media chunk {0} ({1},{2}, #{3} in a row)", chunk.Sid, chunk.Bitrate, chunk.State, m_consecutiveMissedChunks[mediaTypeIndex]); // If we have missed to many, then throw an error if (m_consecutiveMissedChunks[mediaTypeIndex] >= Configuration.Playback.MaxMissingOrCorruptedChunks) { throw new AdaptiveStreamingException(msg); } else { Tracer.Trace(TraceChannel.Error, msg); } mediaQueue.MoveNext(); // No need to verify flag, if we hit end of stream we'll know in 10 milliseconds } DateTime exit2 = DateTime.Now; if ((exit2 - enter).TotalSeconds > 20e-3) { Tracer.Trace(TraceChannel.Timing, "DoSample: long time: {0}", (exit2 - enter).TotalSeconds); } return; } else if (chunk.State != MediaChunk.ChunkState.Parsed) { // We are not parsed, so flag us as missed m_consecutiveMissedChunks[mediaTypeIndex]++; string msg = String.Format( CultureInfo.InvariantCulture, "Failed to {0} media chunk {1} ({2}), #{3} in a row.", chunk.State == MediaChunk.ChunkState.Error ? "download" : "parse", chunk.Sid, chunk.Bitrate, m_consecutiveMissedChunks[mediaTypeIndex]); if (m_consecutiveMissedChunks[mediaTypeIndex] >= Configuration.Playback.MaxMissingOrCorruptedChunks) { throw new AdaptiveStreamingException(msg); } else { Tracer.Trace(TraceChannel.Error, msg); } mediaQueue.MoveNext(); m_workQueue.Enqueue(elem); return; } // If we get here, then we should have a frame. Try to get it from our parser try { if ((frameData = chunk.GetNextFrame()) == null) { // We could not get a frame from our parser, so move to the next chunk flag = mediaQueue.MoveNext(); if (!flag) { // Signal end of the stream DoReportGetSampleCompleted(null, new MediaStreamSample(m_manifestInfo.GetStreamInfoForStreamType(mediaStreamType).Description, null, 0, 0, 0, m_nullAttributes)); return; } } } catch (ChunkNotParsedException) { // We could not get a frame from our parser, so move to the next chunk flag = mediaQueue.MoveNext(); if (!flag) { // Signal end of the stream DoReportGetSampleCompleted(null, new MediaStreamSample(m_manifestInfo.GetStreamInfoForStreamType(mediaStreamType).Description, null, 0, 0, 0, m_nullAttributes)); return; } } } if (chunk.SampleRequestsMissed > 0) { Tracer.Trace(TraceChannel.Error, "Chunk {0} was not available for {1} milliseconds", chunk.Sid, chunk.SampleRequestsMissed * 10); } // Since we have a chunk here, we can reset are missed requests chunk.SampleRequestsMissed = 0; m_consecutiveMissedChunks[mediaTypeIndex] = 0; // Update our buffering state if ((m_bufferingStateMask & mediaTypeMask) != 0) { m_bufferingStateMask &= ~mediaTypeMask; if (m_bufferingStateMask == 0) { FireBufferingDone(); } } // Notify everyone about the bitrate we are using FireOnPlayBitrateChange(chunk.MediaType, chunk.Bitrate, DateTime.Now); // Check to see if we have any DRM attributes Dictionary<MediaSampleAttributeKeys, string> sampleAttributes = new Dictionary<MediaSampleAttributeKeys, string>(); if (frameData.DrmData != null) { sampleAttributes.Add(/*"XCP_MS_SAMPLE_DRM"*/ MediaSampleAttributeKeys.DRMInitializationVector, Convert.ToBase64String(frameData.DrmData)); } // Create the sample that we send to the media element MediaStreamSample sample = new MediaStreamSample( m_manifestInfo.GetStreamInfoForStreamType(mediaStreamType).Description, chunk.DownloadedPiece, frameData.StartOffset, frameData.FrameSize, frameData.Timestamp, sampleAttributes); // Must call if chunk.GetNextFrame is called, which happens only here above mediaQueue.UpdateBufferSizes(); // Report this sample to our heuristics module if (mediaStreamType == MediaStreamType.Video) { m_playbackInfo.SourceFramesPerSecond = chunk.FrameRate; } m_heuristics.OnSampleDelivered(mediaStreamType, chunk.ChunkId, chunk.Bitrate, frameData.Timestamp); // Respond to the media element DoReportGetSampleCompleted(chunk, sample); DateTime exit = DateTime.Now; if ((exit - enter).TotalSeconds > 20e-3) { Tracer.Trace(TraceChannel.Timing, "DoSample: long time: {0}", (exit - enter).TotalSeconds); } }
/// <summary> /// Enqueue a new work item /// </summary> /// <param name="elem">the item to add</param> public void Enqueue(WorkQueueElement elem) { lock (m_queue) { m_queue.Enqueue(elem); if (1 == m_queue.Count) { m_queueHasItemsEvent.Set(); } } }