private static E <LocalStr> RunStartSongTaskExpectWaited(StartSongTask task, CancellationToken token)
        {
            var waitHandle = new InformingEventWaitHandle(false, EventResetMode.AutoReset);

            var t = Task.Run(() => task.RunInternal(waitHandle, token));

            waitHandle.OutputHandle.WaitOne();
            waitHandle.Set();
            return(t.Result);
        }
        public void RunStartSongTaskTest()
        {
            var lck              = new object();
            var queueItem        = new QueueItem(Constants.Resource1AYoutube, new MetaData(Constants.TestUid, Constants.ListId));
            var queueItemGain    = new QueueItem(Constants.Resource1AYoutubeGain, new MetaData(Constants.TestUid, Constants.ListId));
            var playResource     = new PlayResource(queueItem.AudioResource.ResourceId, queueItem.AudioResource, queueItem.MetaData);
            var playResourceGain = new PlayResource(queueItemGain.AudioResource.ResourceId, queueItemGain.AudioResource, queueItemGain.MetaData);

            {
                // Queue item without gain gets it set and update gets invoked
                var loaderContext = new LoaderContextMock();
                var player        = new PlayerMock();

                var task = new StartSongTask(loaderContext, player, Constants.VolumeConfig, lck, queueItem);

                AudioResource changedResource     = null;
                QueueItem     containingQueueItem = null;
                task.OnAudioResourceUpdated += (sender, args) => {
                    changedResource     = args.Resource;
                    containingQueueItem = args.QueueItem;
                };

                var waitHandle  = new InformingEventWaitHandle(false, EventResetMode.AutoReset);
                var tokenSource = new CancellationTokenSource();
                var t           = Task.Run(() => task.RunInternal(waitHandle, tokenSource.Token));

                // Wait that the task reached the first point, cancel
                waitHandle.OutputHandle.WaitOne();
                tokenSource.Cancel();
                waitHandle.Set();

                // Check that it actually failed
                AssertThrowsInnerException <AggregateException, TaskCanceledException>(() => {
                    var _ = t.Result;
                });

                Assert.NotNull(changedResource);
                Assert.NotNull(containingQueueItem);
                Assert.AreSame(containingQueueItem, queueItem);

                var gain = changedResource.Gain;
                Assert.IsTrue(gain.HasValue);
                Assert.AreEqual(gain.Value, VolumeDetectorMock.VolumeSet);
            }

            {
                var loaderContext = new LoaderContextMock();
                var player        = new PlayerMock();

                var task = new StartSongTask(loaderContext, player, Constants.VolumeConfig, lck, queueItem);

                var waitHandle  = new InformingEventWaitHandle(false, EventResetMode.AutoReset);
                var tokenSource = new CancellationTokenSource();
                var t           = Task.Run(() => task.RunInternal(waitHandle, tokenSource.Token));

                // Wait that the task reached the first point, cancel
                waitHandle.OutputHandle.WaitOne();
                lock (lck) {
                    waitHandle.Set();
                    Task.Delay(100);
                    tokenSource.Cancel();
                }

                AssertThrowsInnerException <AggregateException, TaskCanceledException>(() => {
                    var _ = t.Result;
                });
            }

            {
                var player = new PlayerMock();

                var task = new StartSongTask(null, player, Constants.VolumeConfig, null, null);
                PlayInfoEventArgs argsBefore = null;
                PlayInfoEventArgs argsAfter  = null;

                task.BeforeResourceStarted += (sender, args) => {
                    Assert.IsNotNull(args);
                    Assert.AreEqual(args.Invoker, Constants.TestUid);
                    Assert.AreSame(args.MetaData, queueItemGain.MetaData);
                    Assert.AreSame(args.ResourceData, queueItemGain.AudioResource);
                    Assert.AreSame(args.SourceLink, LoaderContextMock.RestoredLink);
                    argsBefore = args;
                };

                task.AfterResourceStarted += (sender, args) => {
                    Assert.IsNotNull(args);
                    Assert.AreEqual(args.Invoker, Constants.TestUid);
                    Assert.AreSame(args.MetaData, queueItemGain.MetaData);
                    Assert.AreSame(args.ResourceData, queueItemGain.AudioResource);
                    Assert.AreSame(args.SourceLink, LoaderContextMock.RestoredLink);
                    argsAfter = args;
                };

                var t = Task.Run(() => task.StartResource(new SongAnalyzerResult {
                    Resource = playResourceGain, RestoredLink = LoaderContextMock.RestoredLink
                }));

                var res = t.Result;

                Assert.IsTrue(res.Ok);
                Assert.AreSame(argsBefore, argsAfter);

                Assert.NotNull(player.PlayArgs.res);
                Assert.NotNull(player.PlayArgs.gain);
                Assert.AreSame(player.PlayArgs.res, playResourceGain);
                Assert.AreEqual(player.PlayArgs.gain, queueItemGain.AudioResource.Gain);
                Assert.AreEqual(player.Volume, 10.0f);
            }

            {
                var player = new PlayerMock();

                var task = new StartSongTask(null, player, Constants.VolumeConfig, null, null);
                var t    = Task.Run(() => task.StartResource(new SongAnalyzerResult {
                    Resource = playResource, RestoredLink = LoaderContextMock.RestoredLink
                }));

                var res = t.Result;

                Assert.IsTrue(res.Ok);

                Assert.NotNull(player.PlayArgs.res);
                Assert.NotNull(player.PlayArgs.gain);
                Assert.AreSame(player.PlayArgs.res, playResource);
                Assert.AreEqual(player.PlayArgs.gain, 0);
                Assert.AreEqual(player.Volume, 10.0f);
            }
        }