From d47d734ad8b09c0100b19e44e51c1147916ba6d7 Mon Sep 17 00:00:00 2001 From: Alan McGovern Date: Mon, 16 Mar 2020 10:00:18 +0000 Subject: [PATCH] [core] Add prebuffering and CancellationTokens to StreamProvider Ensure we download the first and last pieces before giving the user the Stream. This means a streaming player can (probably) play without buffering/stuttering. Prebuffering can be turned off if needed by passing 'false' to the correct CreateStream overload. --- .../Streaming/StreamProviderTests.cs | 7 +- .../MonoTorrent.Streaming/StreamProvider.cs | 71 +++++++++++++++++-- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/MonoTorrent.Tests/Streaming/StreamProviderTests.cs b/src/MonoTorrent.Tests/Streaming/StreamProviderTests.cs index 4f2e5b9a1..76b64db14 100644 --- a/src/MonoTorrent.Tests/Streaming/StreamProviderTests.cs +++ b/src/MonoTorrent.Tests/Streaming/StreamProviderTests.cs @@ -1,6 +1,7 @@ using System; using System.CodeDom; using System.IO; +using System.Threading; using System.Threading.Tasks; using MonoTorrent.Client; using MonoTorrent.Client.PiecePicking; @@ -88,7 +89,7 @@ public async Task CreateStream () { var provider = new StreamProvider (Engine, "testDir", Torrent); await provider.StartAsync (); - using var stream = provider.CreateStreamAsync (Torrent.Files[0]); + using var stream = await provider.CreateStreamAsync (Torrent.Files[0], false, CancellationToken.None); Assert.IsNotNull (stream); } @@ -104,8 +105,8 @@ public async Task CreateStreamTwice () { var provider = new StreamProvider (Engine, "testDir", Torrent); await provider.StartAsync (); - using var stream = provider.CreateStreamAsync (Torrent.Files[0]); - Assert.ThrowsAsync (() => provider.CreateStreamAsync (Torrent.Files[0])); + using var stream = await provider.CreateStreamAsync (Torrent.Files[0], false, CancellationToken.None); + Assert.ThrowsAsync (() => provider.CreateStreamAsync (Torrent.Files[0], false, CancellationToken.None)); } [Test] diff --git a/src/MonoTorrent/MonoTorrent.Streaming/StreamProvider.cs b/src/MonoTorrent/MonoTorrent.Streaming/StreamProvider.cs index fc281dce7..0d8d9ad48 100644 --- a/src/MonoTorrent/MonoTorrent.Streaming/StreamProvider.cs +++ b/src/MonoTorrent/MonoTorrent.Streaming/StreamProvider.cs @@ -201,12 +201,38 @@ public async Task StopAsync () /// /// Creates a which can be used to access the given - /// while it is downloading. This stream is seekable and readable. This stream must be disposed + /// while it is downloading. This stream is seekable and readable. The first and last pieces of + /// this file will be buffered before the stream is created. Finally, this stream must be disposed /// before another stream can be created. /// - /// + /// The file to open /// public Task CreateStreamAsync (TorrentFile file) + => CreateStreamAsync (file, prebuffer: true, CancellationToken.None); + + /// + /// Creates a which can be used to access the given + /// while it is downloading. This stream is seekable and readable. The first and last pieces of + /// this file will be buffered before the stream is created. Finally, this stream must be disposed + /// before another stream can be created. + /// + /// The file to open + /// The cancellation token + /// + public Task CreateStreamAsync (TorrentFile file, CancellationToken token) + => CreateStreamAsync (file, prebuffer: true, token); + + /// + /// Creates a which can be used to access the given + /// while it is downloading. This stream is seekable and readable. The first and last pieces of + /// this file will be buffered before the stream is created if is + /// set to true. Finally, this stream must be disposed before another stream can be created. + /// + /// The file to open + /// True if the first and last piece should be downloaded before the Stream is created. + /// The cancellation token. + /// + public async Task CreateStreamAsync (TorrentFile file, bool prebuffer, CancellationToken token) { if (file == null) throw new ArgumentNullException (nameof (file)); @@ -216,9 +242,19 @@ public Task CreateStreamAsync (TorrentFile file) throw new InvalidOperationException ("You must call StartAsync before creating a stream."); if (ActiveStream != null && !ActiveStream.Disposed) throw new InvalidOperationException ("You must Dispose the previous stream before creating a new one."); + Picker.SeekToPosition (file, 0); ActiveStream = new LocalStream (Manager, file, Picker); - return Task.FromResult (ActiveStream); + + var tcs = CancellationTokenSource.CreateLinkedTokenSource (Cancellation.Token, token); + if (prebuffer) { + ActiveStream.Seek (ActiveStream.Length - 1, SeekOrigin.Begin); + await ActiveStream.ReadAsync (new byte[1], 0, 1, tcs.Token); + + ActiveStream.Seek (0, SeekOrigin.Begin); + await ActiveStream.ReadAsync (new byte[1], 0, 1, tcs.Token); + } + return ActiveStream; } /// @@ -226,11 +262,34 @@ public Task CreateStreamAsync (TorrentFile file) /// while it is downloading. This stream is seekable and readable. This stream must be disposed /// before another stream can be created. /// - /// + /// The file to open + /// + public Task CreateHttpStreamAsync (TorrentFile file) + => CreateHttpStreamAsync (file, prebuffer: true, CancellationToken.None); + + /// + /// Creates a which can be used to access the given + /// while it is downloading. This stream is seekable and readable. This stream must be disposed + /// before another stream can be created. + /// + /// The file to open + /// The cancellation token + /// + public Task CreateHttpStreamAsync (TorrentFile file, CancellationToken token) + => CreateHttpStreamAsync (file, prebuffer: true, token); + + /// + /// Creates a which can be used to access the given + /// while it is downloading. This stream is seekable and readable. This stream must be disposed + /// before another stream can be created. + /// + /// The file to open + /// True if the first and last piece should be downloaded before the Stream is created. + /// The cancellation token /// - public async Task CreateHttpStreamAsync (TorrentFile file) + public async Task CreateHttpStreamAsync (TorrentFile file, bool prebuffer, CancellationToken token) { - var stream = await CreateStreamAsync (file); + var stream = await CreateStreamAsync (file, prebuffer, token); var httpStreamer = new HttpStream (stream); return httpStreamer; }