From f5ecc21ccdf690003d7186254eef161551bb7db6 Mon Sep 17 00:00:00 2001 From: SuRGeoNix Date: Sun, 7 Mar 2021 15:15:26 +0200 Subject: [PATCH] Finalizing new Seek also for network (slow) streams Former-commit-id: 2b24b2c0ee8c742d35d34de8b2e0ff037fa793eb --- Flyleaf/Controls/FlyleafPlayer.cs | 19 +- Flyleaf/MediaFramework/Decoder.cs | 1 + Flyleaf/MediaFramework/DecoderContext.cs | 2 +- Flyleaf/MediaFramework/Demuxer.cs | 44 ++-- Flyleaf/MediaFramework/StreamInfo.cs | 15 +- Flyleaf/MediaRouter.cs | 297 +++++++++++++---------- Flyleaf/TorrentStreamer.cs | 2 +- 7 files changed, 207 insertions(+), 173 deletions(-) diff --git a/Flyleaf/Controls/FlyleafPlayer.cs b/Flyleaf/Controls/FlyleafPlayer.cs index 2ee5be40..11dec4ef 100644 --- a/Flyleaf/Controls/FlyleafPlayer.cs +++ b/Flyleaf/Controls/FlyleafPlayer.cs @@ -481,10 +481,10 @@ private void Player_StatusChanged(object source, StatusChangedArgs e) { if (InvokeRequired) { BeginInvoke(new Action(() => Player_StatusChanged(source, e))); return; } - if (e.status == MediaRouter.Status.PLAYING) - { tblBar.btnPlay.BackgroundImage = Properties.Resources.Pause; tblBar.btnPlay.Text = " "; playingStartedSec = (int)(player.CurTime / 10000000) + 1; timerLoop = 0; } + if (e.status == MediaRouter.Status.PLAYING) + { tblBar.btnPlay.BackgroundImage = Properties.Resources.Pause; tblBar.btnPlay.Text = " "; playingStartedSec = (int)(player.CurTime / 10000000) + 1; timerLoop = 0; } else - { tblBar.btnPlay.BackgroundImage = Properties.Resources.Play; tblBar.btnPlay.Text = ""; } + { tblBar.btnPlay.BackgroundImage = Properties.Resources.Play; tblBar.btnPlay.Text = ""; } } private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { @@ -613,6 +613,8 @@ public void Open(string url) { curHistoryEntry = null; player.Open(url); + tblBar.seekBar.Maximum = 1; + tblBar.seekBar.SetValue(1); } public void MediaFilesReceived(List mediaFiles, List mediaFilesSizes) { @@ -1209,16 +1211,7 @@ private void FlyLeaf_KeyPress (object sender, KeyPressEventArgs e) else if (lvSubs.Visible) FixLvSubs(false); else if (isFullScreen) FullScreenToggle(); } - //{ - // if (picHelp.Visible) - // picHelp.Visible = false; - - // else - // MediaFilesToggle(); - //} - - //lastUserActionTicks = DateTime.UtcNow.Ticks; - } + } private void FlyLeaf_MouseMove (object sender, MouseEventArgs e) { diff --git a/Flyleaf/MediaFramework/Decoder.cs b/Flyleaf/MediaFramework/Decoder.cs index 759b2ab4..03dffab5 100644 --- a/Flyleaf/MediaFramework/Decoder.cs +++ b/Flyleaf/MediaFramework/Decoder.cs @@ -460,6 +460,7 @@ public void Decode() { status = Status.END; Log("EOF"); + if (type == Type.Video) { Log("EOF All"); decCtx.status = Status.END; } break; } diff --git a/Flyleaf/MediaFramework/DecoderContext.cs b/Flyleaf/MediaFramework/DecoderContext.cs index 6302b7d8..ad5c031c 100644 --- a/Flyleaf/MediaFramework/DecoderContext.cs +++ b/Flyleaf/MediaFramework/DecoderContext.cs @@ -328,7 +328,7 @@ public long GetVideoFrame() { MediaFrame mFrame = new MediaFrame(); mFrame.pts = frame->best_effort_timestamp == AV_NOPTS_VALUE ? frame->pts : frame->best_effort_timestamp; - mFrame.timestamp = (long)((mFrame.pts * vDecoder.info.Timebase) + opt.audio.LatencyTicks); + mFrame.timestamp = ((long)(mFrame.pts * vDecoder.info.Timebase) - demuxer.streams[vDecoder.st->index].StartTime) + opt.audio.LatencyTicks; if (mFrame.pts == AV_NOPTS_VALUE) { diff --git a/Flyleaf/MediaFramework/Demuxer.cs b/Flyleaf/MediaFramework/Demuxer.cs index b16e47aa..8bf70436 100644 --- a/Flyleaf/MediaFramework/Demuxer.cs +++ b/Flyleaf/MediaFramework/Demuxer.cs @@ -43,7 +43,7 @@ public unsafe class Demuxer GCHandle decCtxHandle = (GCHandle) ((IntPtr) opaque); DecoderContext decCtx = (DecoderContext) decCtxHandle.Target; - //Console.WriteLine($"** R | { decCtx.demuxer.fmtCtx->pb->pos} - {decCtx.demuxer.ioStream.Position}"); + //Console.WriteLine($"** R | { decCtx.demuxer.fmtCtx->pb->pos} - {decCtx.demuxer.ioStream.Position} | {decCtx.demuxer.fmtCtx->io_repositioned}"); // During seek, it will destroy the sesion on Matroska (requires reopen the format input) if (decCtx.interrupt == 1) { Console.WriteLine("[Stream] Interrupt 1"); return AVERROR_EXIT; } @@ -173,15 +173,25 @@ public int Open(string url, bool doAudio = true, bool doSubs = true, Stream stre * [tls @ 0e691280] The specified session has been invalidated for some reason. * [DECTX AVMEDIA_TYPE_AUDIO] AVMEDIA_TYPE_UNKNOWN - Error[-0005], Msg: I/O error */ + + //av_dict_set(&opt, "rtsp_transport", "tcp", 0); + + //av_dict_set_int(&opt, "rw_timeout", 10 * 1000 * 1000, 0); + //av_dict_set_int(&opt, "timeout", 10 * 1000 * 1000, 0); + + av_dict_set_int(&opt, "stimeout", 10 * 1000 * 1000, 0); // RTSP microseconds timeout + av_dict_set_int(&opt, "reconnect" , 1, 0); // auto reconnect after disconnect before EOF av_dict_set_int(&opt, "reconnect_streamed" , 1, 0); // auto reconnect streamed / non seekable streams av_dict_set_int(&opt, "reconnect_delay_max" , 10, 0); // max reconnect delay in seconds after which to give up //av_dict_set_int(&opt, "reconnect_at_eof", 1, 0); // auto reconnect at EOF | Maybe will use this for another similar issues? | will not stop the decoders (no EOF) - av_dict_set_int(&opt, "stimeout", 10 * 1000 * 1000, 0); // RTSP microseconds timeout + + fmtCtx = avformat_alloc_context(); fmtCtx->interrupt_callback.callback = interruptClbk; fmtCtx->interrupt_callback.opaque = (void*)decCtx.decCtxPtr; + fmtCtx->flags |= AVFMT_FLAG_DISCARD_CORRUPT; if (stream != null) { @@ -259,6 +269,7 @@ public int Open(string url, bool doAudio = true, bool doSubs = true, Stream stre status = Status.READY; pkt = av_packet_alloc(); + return 0; } public void Close() @@ -312,22 +323,22 @@ public void Pause() public int Seek(long ms, bool foreward = false) { if (status == Status.NOTSET) return -1; - if (status == Status.END) status = Status.READY; //Open(url, ...); // Usefull for HTTP + if (status == Status.END) { if (fmtCtx->pb == null) Open(url, decCtx.opt.audio.Enabled, false); else status = Status.READY; } //Open(url, ...); // Usefull for HTTP int ret; long seekTs = CalcSeekTimestamp(ms); - Log("[SEEK] " + Utils.TicksToTime(seekTs * 10)); + Log("[SEEK] " + Utils.TicksToTime(seekTs)); if (foreward) - ret = av_seek_frame(fmtCtx, -1, seekTs, AVSEEK_FLAG_FRAME); + ret = av_seek_frame(fmtCtx, -1, seekTs / 10, AVSEEK_FLAG_FRAME); else - ret = av_seek_frame(fmtCtx, -1, seekTs, AVSEEK_FLAG_BACKWARD); + ret = av_seek_frame(fmtCtx, -1, seekTs / 10, AVSEEK_FLAG_BACKWARD); if (ret < 0) { Log($"[SEEK] [ERROR-1] {Utils.ErrorCodeToMsg(ret)} ({ret})"); - ret = av_seek_frame(fmtCtx, -1, seekTs, AVSEEK_FLAG_ANY); - //if (seek2any) avformat_seek_file(fmtCtx, -1, Int64.MinValue, seekTs, seekTs, AVSEEK_FLAG_ANY); // Same as av_seek_frame ? + ret = av_seek_frame(fmtCtx, -1, seekTs / 10, AVSEEK_FLAG_ANY); + //if (seek2any) avformat_seek_file(fmtCtx, -1, Int64.MinValue, seekTs / 10, seekTs / 10, AVSEEK_FLAG_ANY); // Same as av_seek_frame ? if (ret < 0) Log($"[SEEK] [ERROR-2] {Utils.ErrorCodeToMsg(ret)} ({ret})"); } @@ -337,20 +348,13 @@ public long CalcSeekTimestamp(long ms) { long ticks = ((ms * 10000) + streams[decoder.st->index].StartTime) - decCtx.opt.audio.LatencyTicks; - switch (type) - { - case Type.Video: - return ticks / 10; + if (type == Type.Audio) ticks -= decCtx.opt.audio.DelayTicks; + if (type == Type.Subs ) ticks -= decCtx.opt.subs. DelayTicks; - case Type.Audio: - return (ticks - decCtx.opt.audio.DelayTicks) / 10; + if (ticks < streams[decoder.st->index].StartTime) ticks = streams[decoder.st->index].StartTime;// + (1 * (long)10000); + else if (ticks > streams[decoder.st->index].StartTime + streams[decoder.st->index].DurationTicks) ticks = streams[decoder.st->index].StartTime + streams[decoder.st->index].DurationTicks; - case Type.Subs: - return (ticks - decCtx.opt.subs.DelayTicks) / 10; - - default: - return 0; - } + return ticks; } public void ReSync(long ms = -1) { diff --git a/Flyleaf/MediaFramework/StreamInfo.cs b/Flyleaf/MediaFramework/StreamInfo.cs index 4c5acdc1..f8608a1d 100644 --- a/Flyleaf/MediaFramework/StreamInfo.cs +++ b/Flyleaf/MediaFramework/StreamInfo.cs @@ -12,7 +12,7 @@ public unsafe class StreamInfo // All public AVMediaType Type { get; private set; } public AVCodecID CodecID { get; private set; } - public string CodecIDStr { get { return CodecID.ToString().Replace("AV_CODEC_ID_", ""); } } + public string CodecName { get; private set; } //{ get { return CodecID.ToString().Replace("AV_CODEC_ID_", ""); } } public int StreamIndex { get; private set; } public double Timebase { get; private set; } @@ -47,6 +47,7 @@ public static StreamInfo Get(AVStream* st) si.Type = st->codecpar->codec_type; si.CodecID = st->codecpar->codec_id; + si.CodecName = avcodec_get_name(st->codecpar->codec_id); si.StreamIndex = st->index; si.Timebase = av_q2d(st->time_base) * 10000.0 * 1000.0; si.DurationTicks = (long)(st->duration * si.Timebase); @@ -100,11 +101,11 @@ public string GetDump() string dump = ""; if (Type == AVMEDIA_TYPE_AUDIO) - dump = $"[#{StreamIndex} Audio] {CodecIDStr} {SampleFormatStr}@{Bits} {SampleRate/1000}KHz {ChannelLayoutStr} | {AudioBitRate}"; + dump = $"[#{StreamIndex} Audio] {CodecName} {SampleFormatStr}@{Bits} {SampleRate/1000}KHz {ChannelLayoutStr} | {AudioBitRate}"; else if (Type == AVMEDIA_TYPE_VIDEO) - dump = $"[#{StreamIndex} Video] {CodecIDStr} {PixelFormatStr} {Width}x{Height} @ {FPS.ToString("#.###")} ({AspectRatio.den}/{AspectRatio.num}) | {VideoBitRate}"; + dump = $"[#{StreamIndex} Video] {CodecName} {PixelFormatStr} {Width}x{Height} @ {FPS.ToString("#.###")} ({AspectRatio.den}/{AspectRatio.num}) | {VideoBitRate}"; else if (Type == AVMEDIA_TYPE_SUBTITLE) - dump = $"[#{StreamIndex} Subs] {CodecIDStr} " + (Metadata.ContainsKey("language") ? Metadata["language"] : (Metadata.ContainsKey("lang") ? Metadata["language"] : "")); + dump = $"[#{StreamIndex} Subs] {CodecName} " + (Metadata.ContainsKey("language") ? Metadata["language"] : (Metadata.ContainsKey("lang") ? Metadata["language"] : "")); return dump; } @@ -112,11 +113,11 @@ public string GetDump() public static void Dump(StreamInfo si) { if (si.Type == AVMEDIA_TYPE_AUDIO) - Console.WriteLine($"[#{si.StreamIndex} Audio] {si.CodecIDStr} {si.SampleFormatStr}@{si.Bits} {si.SampleRate/1000}KHz {si.ChannelLayoutStr} | {si.AudioBitRate}"); + Console.WriteLine($"[#{si.StreamIndex} Audio] {si.CodecName} {si.SampleFormatStr}@{si.Bits} {si.SampleRate/1000}KHz {si.ChannelLayoutStr} | {si.AudioBitRate}"); else if (si.Type == AVMEDIA_TYPE_VIDEO) - Console.WriteLine($"[#{si.StreamIndex} Video] {si.CodecIDStr} {si.PixelFormatStr} {si.Width}x{si.Height} @ {si.FPS.ToString("#.###")} ({si.AspectRatio.den}/{si.AspectRatio.num}) | {si.VideoBitRate}"); + Console.WriteLine($"[#{si.StreamIndex} Video] {si.CodecName} {si.PixelFormatStr} {si.Width}x{si.Height} @ {si.FPS.ToString("#.###")} ({si.AspectRatio.den}/{si.AspectRatio.num}) | {si.VideoBitRate}"); else if (si.Type == AVMEDIA_TYPE_SUBTITLE) - Console.WriteLine($"[#{si.StreamIndex} Subs] {si.CodecIDStr} " + (si.Metadata.ContainsKey("language") ? si.Metadata["language"] : (si.Metadata.ContainsKey("lang") ? si.Metadata["language"] : ""))); + Console.WriteLine($"[#{si.StreamIndex} Subs] {si.CodecName} " + (si.Metadata.ContainsKey("language") ? si.Metadata["language"] : (si.Metadata.ContainsKey("lang") ? si.Metadata["language"] : ""))); } public static void Fill(Demuxer demuxer) diff --git a/Flyleaf/MediaRouter.cs b/Flyleaf/MediaRouter.cs index 9a684772..84954bea 100644 --- a/Flyleaf/MediaRouter.cs +++ b/Flyleaf/MediaRouter.cs @@ -328,7 +328,7 @@ private void Initialize(bool andStreamer = true) seeks.Clear(); PauseThreads(); if (openOrBuffer != null && openOrBuffer.IsAlive) { decoder.interrupt = 1; while (openOrBuffer.IsAlive) Thread.Sleep(20); decoder.interrupt = 0; } // Temporary solution - //openOrBuffer?.Abort(); // Will freeze on avformat_open_input + if (seekRequest != null && seekRequest.IsAlive) seekRequest.Abort(); if (andStreamer) torrentStreamer.Dispose(); @@ -446,32 +446,37 @@ private void LoadPlugins() #region Screaming private bool MediaBuffer() { - if (!decoder.isRunning) decoder.Play(); - audioPlayer.ResetClbk(); - audioPlayer.Play(); - - Log("[SCREAMER] Buffering ..."); - torrentStreamer.bitSwarmOpt.EnableBuffering = true; - - // At least 2 video frames & MinQueueSize video packets demuxed - while ((decoder.vDecoder.frames.Count < 2 || (decoder.vDecoder.packets.Count < decoder.opt.demuxer.MinQueueSize && decoder.demuxer.status == MediaFramework.Status.PLAY)) && !decoder.Finished && isPlaying) - Thread.Sleep(15); - - torrentStreamer.bitSwarmOpt.EnableBuffering = false; - Log("[SCREAMER] Buffering Done"); - - decoder.vDecoder.frames.TryDequeue(out vFrame); - if (vFrame == null) { Log("[SCREAMER] [ERROR] No Frames!"); return false; } - decoder.aDecoder.frames.TryDequeue(out aFrame); - decoder.sDecoder.frames.TryDequeue(out sFrame); - SeekTime = -1; - CurTime = vFrame.timestamp; - videoStartTicks = aFrame != null && aFrame.timestamp < vFrame.timestamp - (AudioPlayer.NAUDIO_DELAY_MS * (long)10000) ? aFrame.timestamp : vFrame.timestamp - (AudioPlayer.NAUDIO_DELAY_MS * (long)10000); - startedAtTicks = DateTime.UtcNow.Ticks; - - Log($"[SCREAMER] Started -> {Utils.TicksToTime(CurTime)}"); - - return true; + lock (lockSeek) + { + if (!decoder.isRunning) decoder.Play(); + audioPlayer.ResetClbk(); + audioPlayer.Play(); + + Log("[SCREAMER] Buffering ..."); + torrentStreamer.bitSwarmOpt.EnableBuffering = true; + renderer.NewMessage(OSDMessage.Type.Buffering, $"Loading ...", null, 999999); + + // At least 2 video frames & MinQueueSize video packets demuxed + while ((decoder.vDecoder.frames.Count < 2 || (decoder.vDecoder.packets.Count < decoder.opt.demuxer.MinQueueSize && decoder.demuxer.status == MediaFramework.Status.PLAY)) && !decoder.Finished && isPlaying) + Thread.Sleep(15); + + renderer.ClearMessages(OSDMessage.Type.Buffering); + torrentStreamer.bitSwarmOpt.EnableBuffering = false; + Log("[SCREAMER] Buffering Done"); + + decoder.vDecoder.frames.TryDequeue(out vFrame); + if (vFrame == null) { Log("[SCREAMER] [ERROR] No Frames!"); return false; } + decoder.aDecoder.frames.TryDequeue(out aFrame); + decoder.sDecoder.frames.TryDequeue(out sFrame); + SeekTime = -1; + CurTime = vFrame.timestamp; + videoStartTicks = aFrame != null && aFrame.timestamp < vFrame.timestamp - (AudioPlayer.NAUDIO_DELAY_MS * (long)10000) ? aFrame.timestamp : vFrame.timestamp - (AudioPlayer.NAUDIO_DELAY_MS * (long)10000); + startedAtTicks = DateTime.UtcNow.Ticks; + + Log($"[SCREAMER] Started -> {Utils.TicksToTime(CurTime)} | [V: {Utils.TicksToTime(vFrame.timestamp)}]" + (aFrame == null ? "" : $" [A: {Utils.TicksToTime(aFrame.timestamp)}]")); + + return true; + } } private void Screamer() @@ -509,7 +514,7 @@ private void Screamer() // It will not allowed uncommon formats with slow frame rates to play (maybe check if fps = 1? means dynamic fps?) if (sleepMs > 1000) { - Log("[SCREAMER] Restarting ... (HLS?)"); + Log("[SCREAMER] Restarting ... (HLS?) | + " + Utils.TicksToTime(sleepMs * (long)10000)); MediaBuffer(); continue; } @@ -600,7 +605,7 @@ public void Open(string url, bool isTorrentFile = false) lock (lockOpening) { Log($"Opening {url}"); - renderer.NewMessage(OSDMessage.Type.Open, $"Opening {url}"); + renderer.NewMessage(OSDMessage.Type.Open, $"Opening {url}", null, 999999); status = Status.OPENING; if (isTorrentFile) @@ -699,17 +704,20 @@ public void Open(string url, bool isTorrentFile = false) } } - public void Play() + public void Play(bool todoPlay2 = false) { + if (isSeeking && !todoPlay2) + { + if (beforeSeeking == Status.PLAYING) beforeSeeking = Status.PAUSED; else beforeSeeking = Status.PLAYING; + StatusChanged(this, new StatusChangedArgs(beforeSeeking)); + return; + } + if (!isReady || decoder.isRunning || isPlaying) { StatusChanged?.Invoke(this, new StatusChangedArgs(status)); return; } Interlocked.Exchange(ref SeekTime, -1); renderer.ClearMessages(OSDMessage.Type.Paused); - if (beforeSeeking != Status.PLAYING) renderer.NewMessage(OSDMessage.Type.Play, "Play"); - beforeSeeking = Status.PLAYING; - - renderer.ClearMessages(OSDMessage.Type.Buffering); - + if (beforeSeeking != Status.PLAYING) { renderer.NewMessage(OSDMessage.Type.Play, "Play"); beforeSeeking = Status.PLAYING; } PauseThreads(); status = Status.PLAYING; @@ -731,7 +739,7 @@ public void Play() TimeEndPeriod(1); SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS); status = Status.STOPPED; - StatusChanged?.Invoke(this, new StatusChangedArgs(status)); + if (!isSeeking) StatusChanged?.Invoke(this, new StatusChangedArgs(status)); } }); screamer.SetApartmentState(ApartmentState.STA); @@ -753,124 +761,148 @@ public void Seek(int ms, bool priority = false, bool foreward = false) { Log("Seek Thread Started!"); SeekData seekData; - int seeksCount; bool shouldPlay = false; - int cancelOffsetMs = 500; + int cancelOffsetMs = 1500; // lastSeekMs +- + int stayAliveMs = 5000; // Wait for more seek requests + int networkAbortMs = 250; // Time to wait before aborting the seek request; + int networkDecideMs= 20; // Delay to ensure actual seek request (to avoid more aborts) - //Thread.Sleep(150); // Decide time? - if (!seeks.TryPop(out seekData) || (seekData.ms - cancelOffsetMs <= lastSeekMs && seekData.ms + cancelOffsetMs >= lastSeekMs)) { Log("Seek Thread Cancel!"); return; } - seeksCount = seeks.Count; - - while (true) + while(true) { - Log("Seek Pause All Start"); - status = Status.STOPPING; - if (isTorrent && decoder.demuxer.ioStream != null) + bool seekPop = seeks.TryPop(out seekData); seeks.Clear(); + + if (!seekPop || (seekData.ms - cancelOffsetMs <= lastSeekMs && seekData.ms + cancelOffsetMs >= lastSeekMs)) { - ((BitSwarmLib.BEP.TorrentStream) decoder.demuxer.ioStream).Cancel(); - torrentStreamer.bitSwarmOpt.EnableBuffering = true; + //if (seekPop) Log("Seek Ignored 1 " + Utils.TicksToTime(seekData.ms * (long)10000)); + + // Decide Time (to avoid stop/start seekThread + if (seeks.Count == 0) + { + //Log("Seek Waits | " + shouldPlay); + // mini !isSeeking + SeekTime = -1; //if (isPlaying) SeekTime = -1; + seekWatch.Restart(); + Status beforeSeekingSaved = beforeSeeking; + if (beforeSeeking == Status.PLAYING && shouldPlay) { shouldPlay = false; Play(true); beforeSeekingSaved = Status.PLAYING; } + + do + { + if (beforeSeekingSaved != beforeSeeking) + { + if (beforeSeeking == Status.PLAYING) + Play(true); + else + { + //Log("Seek Pause All Start"); + status = Status.STOPPING; + Utils.EnsureThreadDone(screamer); + decoder.Pause(); + //Log("Seek Pause All Done"); + } + + beforeSeekingSaved = beforeSeeking; + } + + if (seekWatch.ElapsedMilliseconds > stayAliveMs) { Log("Seek Exhausted"); return; } + Render(); + Thread.Sleep(35); + } while (seeks.Count == 0); + seekWatch.Stop(); + } + + continue; } + + if (isPlaying) shouldPlay = true; + //Log("Seek Pause All Start"); + status = Status.STOPPING; Utils.EnsureThreadDone(screamer); decoder.Pause(); - Log("Seek Pause All Done"); + //Log("Seek Pause All Done"); - bool aborted = false; - bool decDone = false; - bool gotIn = false; - lastSeekMs = seekData.ms; - seekWatch.Restart(); + renderer.NewMessage(OSDMessage.Type.Buffering, $"Seeking ...", null, 999999); - Thread decThread = new Thread(() => + if (UrlType != InputType.File && UrlType != InputType.TorrentFile) // Only "Slow" Network Streams (Web/RTSP/Torrent etc.) { - try - { - gotIn = true; - decoder.Seek(seekData.ms, seekData.foreward); - decDone = true; - } - catch (Exception) + Thread.Sleep(networkDecideMs); + if (seeks.Count != 0) { /*Log("Seek Ignores");*/ continue; } + lastSeekMs = seekData.ms; + + int decStatus = -1; + Thread decThread = new Thread(() => { - // TBR | We force ThreadAbortException to avoid destroying ffmpeg's sesions during seek - //if (decoder.demuxer.fmtName == "Matroska / WebM") // | Maybe only on matroska? - if (decoder.demuxer.fmtName != "QuickTime / MOV") - { - Log("Seek Crashed - Reopening"); - decoder.ReOpen(); - } - aborted = true; - } - }); - decThread.IsBackground = true; - decThread.Start(); + torrentStreamer.bitSwarmOpt.EnableBuffering = true; + decStatus = 0; - while (!gotIn) Thread.Sleep(5); + try { decoder.Seek(seekData.ms, seekData.foreward); } + catch (Exception) + { torrentStreamer.bitSwarmOpt.EnableBuffering = false; decStatus = 2; return; } - while (!decDone) - { - if (seekWatch.ElapsedMilliseconds > 250 && seeksCount != seeks.Count && seeks.TryPeek(out SeekData tmpSeekData)) + torrentStreamer.bitSwarmOpt.EnableBuffering = false; + decStatus = 1; + }); + decThread.IsBackground = true; + decThread.Start(); + seekWatch.Restart(); + + while (decStatus < 1) { - if (tmpSeekData.ms - cancelOffsetMs <= lastSeekMs && tmpSeekData.ms + cancelOffsetMs >= lastSeekMs) + if (seekWatch.ElapsedMilliseconds > networkAbortMs) { - Log("No abort - Next Seek Canceled " + Utils.TicksToTime(tmpSeekData.ms * (long)10000)); - seeks.TryPop(out tmpSeekData); + seekPop = seeks.TryPeek(out seekData); + + if (!seekPop || (seekData.ms - cancelOffsetMs <= lastSeekMs && seekData.ms + cancelOffsetMs >= lastSeekMs)) + { + //if (seekPop) Log("Seek Ignored 2 " + Utils.TicksToTime(seekData.ms * (long)10000)); + seeks.Clear(); + } + else + { + seekWatch.Stop(); + + //Log("Seek Abort " + seekWatch.ElapsedMilliseconds); + if (isTorrent && decoder.demuxer.ioStream != null) ((BitSwarmLib.BEP.TorrentStream) decoder.demuxer.ioStream).Cancel(); + + decThread.Abort(); + while (decStatus < 1) { Render(); Thread.Sleep(20); } + + // Only No-index Seek Entries? TBR | Possible ReOpen in new thread + if ((UrlType == InputType.TorrentPart || UrlType == InputType.Other) && decoder.demuxer.fmtName != "QuickTime / MOV") + { + //Log("Seek Crashed - Reopening"); + decoder.ReOpen(); + } + + //Log("Seek Abort Done"); + if (torrentStreamer.bitSwarm != null) torrentStreamer.bitSwarm.FocusAreInUse = false; // Reset after abort thread + break; + } + + seekWatch.Restart(); } - else - { - Log("Seek Abort " + seekWatch.ElapsedMilliseconds); - if (isTorrent && decoder.demuxer.ioStream != null) ((BitSwarmLib.BEP.TorrentStream) decoder.demuxer.ioStream).Cancel(); - decThread.Abort(); - - while (!decDone && !aborted) Thread.Sleep(20); - Log("Seek Abort Done"); - - if (torrentStreamer.bitSwarm != null) torrentStreamer.bitSwarm.FocusAreInUse = false; // Reset after abort thread - break; - } - } - if (!decDone) - { - Render(); // Update OSD SeekTime - Thread.Sleep(20); + if (decStatus < 1) { Render(); Thread.Sleep(20); } else break; } - else - break; - } - - seekWatch.Stop(); - if (decDone) + seekWatch.Stop(); + if (decStatus == 2) continue; + } + else { - ShowOneFrame(); - - if (beforeSeeking == Status.PLAYING) - { - if (seeksCount == seeks.Count) - Play(); - else - shouldPlay = true; - } + lastSeekMs = seekData.ms; + decoder.Seek(seekData.ms, seekData.foreward); } - //if (seeks.IsEmpty) Thread.Sleep(1000); // Decide time? - if (seeksCount == seeks.Count) { Log("Seek Empty"); break; } - - if (!seeks.TryPop(out seekData) || (seekData.ms - cancelOffsetMs <= lastSeekMs && seekData.ms + cancelOffsetMs >= lastSeekMs)) - { - if (shouldPlay) Play(); - Log("Seek Cancel"); - break; - } - seeksCount = seeks.Count; + ShowOneFrame(); + shouldPlay = true; } - - seeks.Clear(); } catch (Exception) { } finally { - if (!isPlaying) torrentStreamer.bitSwarmOpt.EnableBuffering = false; SeekTime = -1; + lastSeekMs = Int32.MinValue; + seekWatch.Stop(); lock (lockSeek) isSeeking = false; Log("Seek Thread Done!"); } @@ -881,6 +913,13 @@ public void Pause() { audioPlayer.ResetClbk(); + if (isSeeking) + { + if (beforeSeeking == Status.PLAYING) beforeSeeking = Status.PAUSED; else beforeSeeking = Status.PLAYING; + StatusChanged(this, new StatusChangedArgs(beforeSeeking)); + return; + } + if (!isReady || !decoder.isRunning || !isPlaying) StatusChanged(this, new StatusChangedArgs(status)); renderer.ClearMessages(OSDMessage.Type.Play); @@ -1175,11 +1214,7 @@ private void PauseThreads(bool andDecoder = true) //Log($"[Pausing All Threads] START"); status = Status.STOPPING; Utils.EnsureThreadDone(screamer); - if (andDecoder) - { - if (isTorrent && decoder.demuxer.ioStream != null) ((BitSwarmLib.BEP.TorrentStream) decoder.demuxer.ioStream).Cancel(); - decoder.Pause(); - } + if (andDecoder) decoder.Pause(); if (hasAudio) audioPlayer.ResetClbk(); status = Status.STOPPED; //Log($"[Pausing All Threads] END"); diff --git a/Flyleaf/TorrentStreamer.cs b/Flyleaf/TorrentStreamer.cs index 9fe5300a..7179a4ab 100644 --- a/Flyleaf/TorrentStreamer.cs +++ b/Flyleaf/TorrentStreamer.cs @@ -158,7 +158,7 @@ private bool DownloadNext() return false; } - private void OnFinishing(object source, BitSwarm.FinishingArgs e) { Log("Download of " + Torrent.file.paths[fileIndexNext == -1 ? fileIndex : fileIndexNext] + " finished"); e.Cancel = DownloadNext(); if (!e.Cancel) Log("Stopped");} + private void OnFinishing(object source, BitSwarm.FinishingArgs e) { player.UrlType = InputType.TorrentFile; Log("Download of " + Torrent.file.paths[fileIndexNext == -1 ? fileIndex : fileIndexNext] + " finished"); e.Cancel = DownloadNext(); if (!e.Cancel) Log("Stopped");} private void StatsUpdated(object source, BitSwarm.StatsUpdatedArgs e) { player.renderer.NewMessage(OSDMessage.Type.TorrentStats, $"{(downloadNextStarted ? "(N) " : "")}D: {e.Stats.PeersDownloading} | W: {e.Stats.PeersChoked}/{e.Stats.PeersInQueue} | {String.Format("{0:n0}", (e.Stats.DownRate / 1024))} KB/s | {e.Stats.Progress}%"); } private void MetadataReceived(object source, BitSwarm.MetadataReceivedArgs e) {