/// <summary>
/// Internals the seek input.
/// </summary>
/// <param name="renderTime">The render time.</param>
private void InternalSeekInput(decimal renderTime)
{
if (IsLiveStream)
{
if (LeadingFramesCache.IsEmpty == false)
{
RealtimeClock.Seek(LeadingFramesCache.FirstFrameTime);
}
return;
}
#if DEBUG
var seekStopwatch = new System.Diagnostics.Stopwatch();
seekStopwatch.Start();
#endif
if (renderTime < StartTime)
{
renderTime = StartTime;
}
RealtimeClock.Seek(renderTime);
var allowedThreshold = Constants.SeekThresholdSeconds;
var seekOffsetLength = Constants.SeekOffsetSeconds;
var seekTime = renderTime - seekOffsetLength;
var maxSeekStartTime = seekTime - allowedThreshold;
var bufferedLeadingFrames = new FFmpegMediaFrameCache(LeadingFramesCache);
var bufferedLaggingFrames = new FFmpegMediaFrameCache(LaggingFramesCache);
var outerLoopCount = 0;
var innerLoopCount = 0;
var frameReleaseCount = 0;
var doSeekInStream = true;
var doSeekByPullingFrames = true;
var seekFlag = 0;
var seekFrameResult = 0;
var startTime = DateTime.UtcNow;
var lastFailedTimestamp = long.MinValue;
var seekToLastFrame = false;
var seekTimeBase = LeadingFramesCache.Type == MediaFrameType.Video ? InputVideoStream->time_base : InputAudioStream->time_base;
var seekStreamIndex = LeadingFramesCache.Type == MediaFrameType.Video ? InputVideoStream->index : InputAudioStream->index;
var leadingFrameIndex = -1;
try
{
while (doSeekInStream)
{
outerLoopCount++;
if (seekTime < StartTime)
{
seekTime = StartTime;
}
if (lastFailedTimestamp == StartDts)
{
if (LeadingFramesCache.IsEmpty == false)
{
RealtimeClock.Seek(LeadingFramesCache.FirstFrameTime);
}
ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedCritical,
string.Format("Target Postion @ {0:0.000}s has already failed to seek. First DTS {1} also failed and will not retry.", seekTime, StartDts)));
return;
}
var targetTimestamp = Helper.SecondsToTimestamp(seekTime, seekTimeBase);
if (lastFailedTimestamp == targetTimestamp)
{
ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedWillRetry,
string.Format("Target Postion @ {0:0.000}s has already failed to seek. Target timestamp will now be First DTS {1}.", seekTime, StartDts)));
targetTimestamp = StartDts;
}
seekFlag = (seekTime < renderTime || seekTime <= StartTime ? (int)ffmpeg.AVSEEK_FLAG_BACKWARD : 0) | 0; // FFmpegInvoke.AVSEEK_FLAG_ANY;
//seekFlag = FFmpegInvoke.AVSEEK_FLAG_BACKWARD; // | FFmpegInvoke.AVSEEK_FLAG_ANY;
seekFrameResult = ffmpeg.av_seek_frame(InputFormatContext, seekStreamIndex, targetTimestamp, seekFlag); // significantly faster than seek_file
//seekFrameResult = FFmpegInvoke.avformat_seek_file(InputFormatContext, streamIndex, long.MinValue, targetTimestamp, long.MaxValue, seekFlag);
if (seekFrameResult < Constants.SuccessCode)
{
if (LeadingFramesCache.IsEmpty == false)
{
RealtimeClock.Seek(LeadingFramesCache.FirstFrameTime);
}
var errorMessage = Helper.GetFFmpegErrorMessage(seekFrameResult);
ErrorOccurredCallback(this, new MediaPlaybackException(MediaPlaybackErrorSources.InternalSeekInput, MediaPlaybackErrorCode.SeekFailedFFmpeg,
string.Format("FFmpeg av_seek_frame @ {1:0.000}: Failed with error code {0}. {2}", seekFrameResult, seekTime, errorMessage)));
return;
}
else
{
if (VideoCodecContext != null)
{
ffmpeg.avcodec_flush_buffers(VideoCodecContext);
}
if (AudioCodecContext != null)
{
ffmpeg.avcodec_flush_buffers(AudioCodecContext);
}
}
leadingFrameIndex = -1;
bufferedLeadingFrames.Clear();
bufferedLaggingFrames.Clear();
doSeekInStream = false;
doSeekByPullingFrames = true;
while (doSeekByPullingFrames)
{
innerLoopCount++;
var frame = this.PullMediaFrame();
if (frame != null)
{
if (frame.StartTime < maxSeekStartTime)
{
frame.EnqueueRelease();
frameReleaseCount++;
continue;
}
if (frame.Type == bufferedLeadingFrames.Type)
{
leadingFrameIndex++;
if (leadingFrameIndex == 0 && frame.Type == bufferedLeadingFrames.Type && frame.StartTime - frame.Duration > maxSeekStartTime && maxSeekStartTime > 0M)
{
seekTime -= seekOffsetLength;
frame.EnqueueRelease();
doSeekInStream = true;
lastFailedTimestamp = targetTimestamp;
break;
}
// We are Full minus 1 at this point. We'll stop buffering
if (bufferedLeadingFrames.Count >= bufferedLeadingFrames.Capacity - 1)
{
doSeekByPullingFrames = false;
}
bufferedLeadingFrames.Add(frame);
}
else if (frame.Type == bufferedLaggingFrames.Type)
{
// add the lagging frame no matter what
if (bufferedLaggingFrames.IsFull)
{
bufferedLaggingFrames.RemoveFirst();
}
bufferedLaggingFrames.Add(frame);
}
// Find out if we have the frame
var seekFrameIndex = bufferedLeadingFrames.IndexOf(renderTime, true);
var minimumFrameCount = (seekFrameIndex - 1) * 2;
// if we have more than enough frames in the buffer or we have reached a full or end condition, stop buffering frames
if (seekFrameIndex > 0)
{
if (bufferedLeadingFrames.Count >= minimumFrameCount || bufferedLeadingFrames.IsFull || IsAtEndOfStream)
{
doSeekByPullingFrames = false;
}
}
}
// We're already padt the end of the stream. Natural duration was wron for the leading frames cache.
if (IsAtEndOfStream && bufferedLeadingFrames.Count <= 0)
{
doSeekInStream = true;
seekTime -= seekOffsetLength;
maxSeekStartTime = seekTime - allowedThreshold;
seekToLastFrame = true;
}
if (doSeekInStream)
{
break;
}
if (doSeekByPullingFrames == false || IsAtEndOfStream)
{
LeadingFramesCache.Replace(bufferedLeadingFrames);
LaggingFramesCache.Replace(bufferedLaggingFrames);
if (seekToLastFrame && LeadingFramesCache.Count > 0)
{
RealtimeClock.Seek(LeadingFramesCache.LastFrameTime);
}
return;
}
}
}
}
finally
{
#if DEBUG
seekStopwatch.Stop();
SeekTimes.Add(seekStopwatch.ElapsedMilliseconds);
InnerLoopCounts.Add(innerLoopCount);
System.Diagnostics.Debug.WriteLine("Seek @ {6:0.000} = Long: {0:00}\t Short: {1:000}\t Short (AVG): {2:0.000}\t Waste Count: {3:000}\t Elapsed: {4}\tElapsed (AVG): {5:0.000}",
outerLoopCount, innerLoopCount, InnerLoopCounts.Average(), frameReleaseCount, seekStopwatch.ElapsedMilliseconds, SeekTimes.Average(), renderTime);
#endif
}
}