Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix Youtube streaming #149 #153

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Create-Release.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Write-Host "Building Espera..." -ForegroundColor Green

Function GetMSBuildExe {
[CmdletBinding()]
$DotNetVersion = "4.0"
$RegKey = "HKLM:\software\Microsoft\MSBuild\ToolsVersions\$DotNetVersion"
$MSBuildToolsVersion = "14.0"
$RegKey = "HKLM:\software\Microsoft\MSBuild\ToolsVersions\$MSBuildToolsVersion"
$RegProperty = "MSBuildToolsPath"
$MSBuildExe = Join-Path -Path (Get-ItemProperty $RegKey).$RegProperty -ChildPath "msbuild.exe"
Return $MSBuildExe
Expand Down
31 changes: 25 additions & 6 deletions Espera.Core.Tests/AudioPlayerTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -29,7 +30,10 @@ public void CanSetVolumeAfterConstruction()
[Fact]
public async Task StopsCurrentMediaPlayerWhenSwitchingAndPlaying()
{
var audioPlayer = new AudioPlayer();
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var audioPlayer = new AudioPlayer(streamProxy);

var oldMediaPlayer = Substitute.For<IMediaPlayerCallback>();
var newMediaPlayer = Substitute.For<IMediaPlayerCallback>();
Expand All @@ -56,7 +60,10 @@ public class TheLoadAsyncMethod
[Fact]
public async Task DisposesCurrentAudioPlayerIfNewOneRegistered()
{
var audioPlayer = new AudioPlayer();
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var audioPlayer = new AudioPlayer(streamProxy);

var oldMediaPlayer = Substitute.For<IMediaPlayerCallback, IDisposable>();
var newMediaPlayer = Substitute.For<IMediaPlayerCallback, IDisposable>();
Expand All @@ -76,7 +83,10 @@ public async Task DisposesCurrentAudioPlayerIfNewOneRegistered()
[Fact]
public async Task LoadsIntoAudioPlayerIfSongIsAudio()
{
var audioPlayer = new AudioPlayer();
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var audioPlayer = new AudioPlayer(streamProxy);
var mediaPlayerCallback = Substitute.For<IMediaPlayerCallback>();
audioPlayer.RegisterAudioPlayerCallback(mediaPlayerCallback);

Expand All @@ -91,7 +101,10 @@ public async Task LoadsIntoAudioPlayerIfSongIsAudio()
[Fact]
public async Task LoadsIntoVideoPlayerIfSongIsVideo()
{
var audioPlayer = new AudioPlayer();
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var audioPlayer = new AudioPlayer(streamProxy);
var mediaPlayerCallback = Substitute.For<IMediaPlayerCallback>();
audioPlayer.RegisterVideoPlayerCallback(mediaPlayerCallback);

Expand All @@ -106,7 +119,10 @@ public async Task LoadsIntoVideoPlayerIfSongIsVideo()
[Fact]
public async Task StopsCurrentPlayback()
{
var audioPlayer = new AudioPlayer();
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var audioPlayer = new AudioPlayer(streamProxy);

var states = audioPlayer.PlaybackState.CreateCollection();

Expand All @@ -128,7 +144,10 @@ public class TheRegisterAudioPlayerMethod
[Fact]
public async Task DisposesDanglingAudioPlayer()
{
var audioPlayer = new AudioPlayer();
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var audioPlayer = new AudioPlayer(streamProxy);
var mediaPlayer = Substitute.For<IMediaPlayerCallback>();
audioPlayer.RegisterAudioPlayerCallback(mediaPlayer);
await audioPlayer.LoadAsync(Helpers.SetupSongMock());
Expand Down
9 changes: 8 additions & 1 deletion Espera.Core.Tests/Espera.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Lager.0.4.2\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Lager.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Reactive.Testing, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Rx-Testing.2.2.5\lib\net45\Microsoft.Reactive.Testing.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
Expand Down Expand Up @@ -140,6 +146,7 @@
<Compile Include="SoundCloudSongFinderTest.cs" />
<Compile Include="SoundCloudSongTest.cs" />
<Compile Include="TagSanitizerTest.cs" />
<Compile Include="UriRewriteTest.cs" />
<Compile Include="YoutubeSongFinderTest.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
4 changes: 3 additions & 1 deletion Espera.Core.Tests/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reactive.Threading.Tasks;
using System.Text;
using System.Threading.Tasks;
using Espera.Core.Audio;
using Espera.Core.Management;
using Espera.Core.Settings;
using Microsoft.Reactive.Testing;
Expand Down Expand Up @@ -74,13 +75,14 @@ public static async Task AwaitInitializationAndUpdate(this Library library)
}

public static Library CreateLibrary(CoreSettings settings = null, ILibraryReader reader = null, ILibraryWriter writer = null,
IFileSystem fileSystem = null, ILocalSongFinder localSongFinder = null)
IFileSystem fileSystem = null, ILocalSongFinder localSongFinder = null, AudioPlayer audioPlayer = null)
{
return new LibraryBuilder().WithReader(reader)
.WithWriter(writer)
.WithSettings(settings)
.WithFileSystem(fileSystem)
.WithSongFinder(localSongFinder)
.WithAudioPlayer(audioPlayer)
.Build();
}

Expand Down
10 changes: 9 additions & 1 deletion Espera.Core.Tests/LibraryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class LibraryBuilder
private CoreSettings settings;
private ILocalSongFinder songFinder;
private ILibraryWriter writer;
private AudioPlayer audioPlayer;

public Library Build()
{
Expand All @@ -35,7 +36,8 @@ public Library Build()
this.writer ?? Substitute.For<ILibraryWriter>(),
this.settings ?? new CoreSettings(new InMemoryBlobCache()),
this.fileSystem ?? new MockFileSystem(),
x => this.songFinder ?? SetupDefaultLocalSongFinder());
x => this.songFinder ?? SetupDefaultLocalSongFinder(),
this.audioPlayer ?? new AudioPlayer());

Guid accessToken = library.LocalAccessControl.RegisterLocalAccessToken();

Expand All @@ -49,6 +51,12 @@ public Library Build()
return library;
}

public LibraryBuilder WithAudioPlayer(AudioPlayer player)
{
this.audioPlayer = player;
return this;
}

public LibraryBuilder WithAudioPlayer(IMediaPlayerCallback player)
{
this.audioPlayerCallback = player;
Expand Down
40 changes: 32 additions & 8 deletions Espera.Core.Tests/LibraryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ public sealed class LibraryTest
[Fact]
public async Task CanPlayAWholeBunchOfSongs()
{
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var song = new LocalSong("C://", TimeSpan.Zero);

using (Library library = new LibraryBuilder().WithPlaylist().Build())
using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(new AudioPlayer(streamProxy)).Build())
{
var awaiter = library.PlaybackState.Where(x => x == AudioPlayerState.Playing)
.Select((x, i) => i + 1)
Expand Down Expand Up @@ -231,9 +234,12 @@ public class TheContinueSongAsyncMethod
[Fact]
public async Task CallsAudioPlayerPlay()
{
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var audioPlayerCallback = Substitute.For<IMediaPlayerCallback>();

using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayerCallback).Build())
using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayerCallback).WithAudioPlayer(new AudioPlayer(streamProxy)).Build())
{
Guid token = library.LocalAccessControl.RegisterLocalAccessToken();

Expand Down Expand Up @@ -427,7 +433,10 @@ public async Task JumpsOverCorruptedSong()
}
}));

using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayer).Build())
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayer).WithAudioPlayer(new AudioPlayer(streamProxy)).Build())
{
Song[] songs = Helpers.SetupSongMocks(2);

Expand All @@ -444,7 +453,10 @@ public async Task JumpsOverCorruptedSong()
[Fact]
public async Task PlaysMultipleSongsInARow()
{
using (Library library = Helpers.CreateLibrary())
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

using (Library library = Helpers.CreateLibrary(audioPlayer: new AudioPlayer(streamProxy)))
{
var conn = library.PlaybackState.Where(x => x == AudioPlayerState.Playing)
.Take(2)
Expand All @@ -460,9 +472,12 @@ public async Task PlaysMultipleSongsInARow()
[Fact]
public async Task SmokeTest()
{
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

var audioPlayer = Substitute.For<IMediaPlayerCallback>();

using (Library library = new LibraryBuilder().WithAudioPlayer(audioPlayer).Build())
using (Library library = new LibraryBuilder().WithAudioPlayer(audioPlayer).WithAudioPlayer(new AudioPlayer(streamProxy)).Build())
{
Song song = Helpers.SetupSongMock();

Expand Down Expand Up @@ -541,7 +556,10 @@ public class ThePlaySongAsyncMethod
[Fact]
public async Task PlaysNextSongAutomatically()
{
using (Library library = new LibraryBuilder().WithPlaylist().Build())
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(new AudioPlayer(streamProxy)).Build())
{
Guid token = library.LocalAccessControl.RegisterLocalAccessToken();

Expand All @@ -564,7 +582,10 @@ public async Task ResetsSongIsCorruptedIfPlayingIsWorking()
var audioPlayerCallback = Substitute.For<IMediaPlayerCallback>();
audioPlayerCallback.LoadAsync(Arg.Any<Uri>()).Returns(Observable.Throw<Unit>(new SongLoadException()).ToTask());

using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayerCallback).Build())
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(audioPlayerCallback).WithAudioPlayer(new AudioPlayer(streamProxy)).Build())
{
Guid accessToken = library.LocalAccessControl.RegisterLocalAccessToken();

Expand Down Expand Up @@ -788,7 +809,10 @@ public void ThrowsArgumentNullExceptionIfSongListIsNull()
[Fact]
public async Task WhileSongIsPlayingStopsCurrentSong()
{
using (Library library = new LibraryBuilder().WithPlaylist().Build())
var streamProxy = Substitute.For<IHttpsProxyService>();
streamProxy.GetProxiedUri(new Uri("https://foobar.com")).ReturnsForAnyArgs(x => x.Args().First());

using (Library library = new LibraryBuilder().WithPlaylist().WithAudioPlayer(new AudioPlayer(streamProxy)).Build())
{
Guid token = library.LocalAccessControl.RegisterLocalAccessToken();

Expand Down
57 changes: 57 additions & 0 deletions Espera.Core.Tests/UriRewriteTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Net;
using Espera.Core.Audio;
using Xunit;

namespace Espera.Core.Tests
{
public class UriRewriteTest : IDisposable
{
private readonly IHttpsProxyService httpsProxyService;

public UriRewriteTest()
{
httpsProxyService = new HttpsProxyService();
}

[Fact]
public void YoutubeUrisAreProxied()
{
var song = new YoutubeSong("https://youtube.com", TimeSpan.Zero);
var path = song.GetType().GetField("playbackPath", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
path.SetValue(song, "https://youtube.com/?a=c&");
var proxiedUri = song.GetSafePlaybackPath(httpsProxyService);
Assert.Equal("127.0.0.1", proxiedUri.Host);
Assert.Equal("/?remoteurl=" + WebUtility.UrlEncode("https://youtube.com/?a=c&"), proxiedUri.PathAndQuery);
}

[Fact]
public void SoundCloudUrisAreRewritten()
{
var song = new SoundCloudSong("https://soundcloud.com/foobar", "https://api.soundlcoud.com/foobar");
var safePath = song.GetSafePlaybackPath(httpsProxyService);
Assert.Equal("http://api.soundlcoud.com/foobar", safePath.ToString());
}


[Fact]
public void LocalUrisAreNotRewritten()
{
var song = new LocalSong("C:\\some\\file.mp3", TimeSpan.Zero);
Assert.Equal(new Uri("C:\\some\\file.mp3"), song.GetSafePlaybackPath(httpsProxyService));
}

[Fact]
public void ThrowsForNullUriObject()
{
var song = new YoutubeSong("https://youtube.com", TimeSpan.Zero);
Assert.Throws<ArgumentNullException>(() => song.GetSafePlaybackPath(null));
}


public void Dispose()
{
httpsProxyService.Dispose();
}
}
}
1 change: 1 addition & 0 deletions Espera.Core.Tests/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<package id="akavache.core" version="4.1.2" targetFramework="net45" />
<package id="Espera-Network" version="1.0.36" targetFramework="net45" />
<package id="Lager" version="0.4.2" targetFramework="net45" />
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" />
<package id="NSubstitute" version="1.8.1.0" targetFramework="net45" />
<package id="ReactiveMarrow" version="0.5.1" targetFramework="net45" />
Expand Down
8 changes: 6 additions & 2 deletions Espera.Core/Audio/AudioPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reactive.Threading.Tasks;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -22,13 +23,16 @@ public sealed class AudioPlayer : IEnableLogger
private readonly SemaphoreSlim gate;
private readonly BehaviorSubject<Song> loadedSong;
private readonly BehaviorSubject<AudioPlayerState> playbackState;
private readonly IHttpsProxyService httpsProxyService;
private IMediaPlayerCallback audioPlayerCallback;
private IMediaPlayerCallback currentCallback;
private bool disposeCurrentAudioCallback;
private IMediaPlayerCallback videoPlayerCallback;

internal AudioPlayer()
internal AudioPlayer(IHttpsProxyService httpsProxyService = null)
{

this.httpsProxyService = httpsProxyService ?? Locator.Current.GetService<IHttpsProxyService>();
this.audioPlayerCallback = new DummyMediaPlayerCallback();
this.videoPlayerCallback = new DummyMediaPlayerCallback();
this.currentCallback = new DummyMediaPlayerCallback();
Expand Down Expand Up @@ -142,7 +146,7 @@ internal async Task LoadAsync(Song song)

try
{
await this.currentCallback.LoadAsync(new Uri(this.loadedSong.Value.PlaybackPath));
await this.currentCallback.LoadAsync(song.GetSafePlaybackPath(httpsProxyService));

this.finishSubscription.Disposable = this.currentCallback.Finished.FirstAsync()
.SelectMany(_ => this.Finished().ToObservable())
Expand Down
Loading