public async Task <bool> PauseAsync(PlaybackSession session)
        {
            var jobId  = session.CurrentSongPlayback.JobId;
            var jobKey = new JobKey(jobId);
            var job    = await _jobScheduler.GetJobDetail(jobKey);

            if (job.JobType == typeof(SongFinishedJob))
            {
                var songTracker = session.CurrentSongPlayback;

                TimeSpan pausedAtDuration = (songTracker.State == PlaybackState.Resumed && songTracker.ResumedUtc.HasValue && songTracker.PausedAtMs.HasValue)
                                        ? DateTime.UtcNow - songTracker.ResumedUtc.Value + TimeSpan.FromMilliseconds(songTracker.PausedAtMs.Value)
                                        : DateTime.UtcNow - songTracker.StartedUtc;

                songTracker.PausedAtMs = Convert.ToInt32(pausedAtDuration.TotalMilliseconds);
                songTracker.PausedUtc  = DateTime.UtcNow;
                songTracker.State      = PlaybackState.Paused;

                await _songRepo.Upsert(songTracker);

                await _jobScheduler.DeleteJob(jobKey);

                await _playbackService.PauseSpotifyUserPlaybackAsync(session.JoinedUsers.Select(u => u.Id));

                return(true);
            }
            else
            {
                throw new InvalidOperationException(
                          "PlaybackSession cant be paused if its job type is not SongFinishedJob");
            }
        }
        public async Task <bool> SkipAsync(PlaybackSession session)
        {
            var jobId  = session.CurrentSongPlayback.JobId;
            var jobKey = new JobKey(jobId);
            var job    = await _jobScheduler.GetJobDetail(jobKey);

            if (job != null)
            {
                session.CurrentSongPlayback.State = PlaybackState.Skipped;
                await _songRepo.Upsert(session.CurrentSongPlayback);

                var triggers = await _jobScheduler.GetTriggersOfJob(jobKey);

                await _jobScheduler.TriggerJob(jobKey, job.JobDataMap);

                await _jobScheduler.UnscheduleJobs(triggers.Select(t => t.Key).ToImmutableList());

                return(true);
            }
            else
            {
                // Start new playback if there is no scheduled job
                await StartNewPlayback(session);

                return(true);
            }
        }
示例#3
0
        private async Task <IEnumerable <PlaylistTrack> > GetNextTracksAsync(PlaybackSession session, string finishedSongUri = null, int previewCount = 2)
        {
            // Call spotifyApi to get playlist from playlistId
            // TODO this will have performance issues for larger playlists. Instead use the limit: and offset: params

            List <PlaylistTrack> playlistTracks;

            using (var client = await _spotifyApiFactory.CreateAppClient())
            {
                playlistTracks = (await client.GetPlaylistTracksAsync(null, session.SpotifyPlaylistId))
                                 .Items;
            }

            // Get next track by finding the one after the finished song uri
            var finishedIndex = playlistTracks.FindIndex(t => t.Track.Uri == finishedSongUri);

            // Next song, or start back at 0
            var nextIndex = finishedIndex >= playlistTracks.Count - 1 ? 0 : finishedIndex + 1;

            // Take next track, plus preview tracks
            var requiredCount = 1 + previewCount;

            // Get the next song, and the previews
            var nextTracks = playlistTracks.Skip(nextIndex).Take(requiredCount).ToList();

            // If we are at the end of the playlist, get the previews from the start
            if (nextTracks.Count() < requiredCount)
            {
                var take = requiredCount - nextTracks.Count();
                nextTracks.AddRange(playlistTracks.Take(take));
            }

            return(nextTracks);
        }
示例#4
0
        public void PlaySoundInDevice(int deviceNumber)
        {
            try
            {
                var w = _waveOuts[deviceNumber];
                w.Stop();

                if (outputDevices.ContainsKey(deviceNumber))
                {
                    outputDevices[deviceNumber].WaveOut.Dispose();
                    outputDevices[deviceNumber].WaveStream.Dispose();
                }

                WaveStream waveStream = IsMp3(_fileName)
                    ? (WaveStream) new Mp3FileReader(_fileName)
                    : new WaveFileReader(_fileName);

                LoopStream loop = new LoopStream(waveStream);

                // hold onto the WaveOut and  WaveStream so we can dispose them later
                outputDevices[deviceNumber] = new PlaybackSession {
                    WaveOut = w, WaveStream = loop
                };

                w.Init(loop);
                w.Play();
            }
            catch (Exception)
            {
                //
            }
        }
示例#5
0
 // Constructor
 internal EventHandlerDelegate(PlaybackSession session, Tuning tuning, OverlaySurface overlaySurface, UserInterface userInterface)
 {
     _tuning                = tuning;
     _session               = session;
     _overlaySurface        = overlaySurface;
     _userInterface         = userInterface;
     _eKeyboardHandler      = null;
     _eChannelChangeHandler = null;
 }
示例#6
0
 public TimeSpan GetDuration()
 {
   PlaybackSession session = _playbackProcessor.PlaybackSession;
   if (session == null)
     return TimeSpan.Zero;
   IInputSource source = session.CurrentInputSource;
   if (source == null)
     return TimeSpan.Zero;
   return source.Length;
 }
        private async Task PopulatePropertiesAsync(PlaybackSession session, PlaybackSessionQueryOptions queryOptions = null)
        {
            // Looks like this related property is being eager loaded. The inner if was never hit in testing
            if (session != null &&
                !string.IsNullOrWhiteSpace(session.CurrentSongPlaybackId) &&
                queryOptions != null)
            {
                var currentPlaybackId = session.CurrentSongPlaybackId;
                var playback          = await _db.SongPlaybackTrackers.FirstOrDefaultAsync(m => m.Id == currentPlaybackId);

                session.CurrentSongPlayback = playback ?? throw new Exception("Current playback cannot be null");
            }
        }
        public async Task <bool> ResumeAsync(PlaybackSession session)
        {
            if (session.CurrentSongPlayback != null && session.CurrentSongPlayback.State == PlaybackState.Paused && session.CurrentSongPlayback.PausedAtMs.HasValue)
            {
                var userIds = session.JoinedUsers.Select(u => u.Id);

                // Nothing to do if there is no more joined users, or playlist
                if (!session.JoinedUsers.Any() ||
                    string.IsNullOrWhiteSpace(session.SpotifyPlaylistId))
                {
                    _logger.Log(LogLevel.Information, "No more users on {sessionId}. Halting playback", session.Id);
                    return(false);
                }

                var songTracker = session.CurrentSongPlayback;

                songTracker.State      = PlaybackState.Resumed;
                songTracker.ResumedUtc = DateTime.UtcNow;

                await _songRepo.Upsert(songTracker);

                var startPlaybackTask = _playbackService.StartSpotifyUserPlaybackAsync(userIds, session.CurrentSongPlayback.SpotifySongUri, session.CurrentSongPlayback.PausedAtMs ?? 0);

                // Spawn next job
                var jobId = $"SongFinishedJob-{Guid.NewGuid().ToString()}";
                session.CurrentSongPlayback.JobId = jobId;
                await _songRepo.Upsert(session.CurrentSongPlayback);

                var job = JobBuilder.Create <SongFinishedJob>()
                          .WithIdentity(jobId)
                          .WithDescription($"SongFinishedJob SessionId: {session.Id}, PlaylistId: {session.SpotifyPlaylistId}, SongUri: {session.CurrentSongPlayback.SpotifySongUri}")
                          .UsingJobData("PlaybackSessionId", session.Id)
                          .UsingJobData("SpotifySongUri", session.CurrentSongPlayback.SpotifySongUri)
                          .Build();
                var trigger = TriggerBuilder.Create()
                              .ForJob(job)
                              .StartAt(session.CurrentSongPlayback.ExpectedFinishUtc)
                              .Build();
                var scheduleJobTask = _jobScheduler.ScheduleJob(job, trigger);

                await Task.WhenAll(startPlaybackTask, scheduleJobTask);

                return(true);
            }
            else
            {
                throw new InvalidOperationException(
                          "PlaybackSession cant be resumed if it is not paused");
            }
        }
 private async Task StartNewPlayback(PlaybackSession session)
 {
     var jobId = $"SongFinishedJob-{Guid.NewGuid().ToString()}";
     var job   = JobBuilder.Create <SongFinishedJob>()
                 .WithIdentity(jobId)
                 .WithDescription($"SongFinishedJob SessionId: {session.Id}")
                 .UsingJobData("PlaybackSessionId", session.Id.ToString())
                 .Build();
     var trigger = TriggerBuilder.Create()
                   .ForJob(job)
                   .StartNow()
                   .Build();
     await _jobScheduler.ScheduleJob(job, trigger);
 }
示例#10
0
        // Constructor
        internal UserInterface(ItvApp.AddIn addin)
        {
            _addin = addin;

            _overlaySurface = _addin.OverlaySurface;
            _videoSurface   = _addin.VideoSurface;
            _tuning         = _addin.Tuning;
            _session        = _addin.Session;

            // create the object to receive keyboard hits.
            _eventHandler = new EventHandlerDelegate(_session, _tuning, _overlaySurface, this);

            // create a new audiostreamswticher
            _audioStreamSwitcher = new AudioStreamSwitcher(addin);
        }
示例#11
0
        public async Task SetPlaybackSession(SynthbotUser user, PlaybackSession session)
        {
            await _dbSignal.WaitAsync();

            try
            {
                _db.Users.Attach(user);
                user.ActivePlaybackSession = session;
                await _db.SaveChangesAsync();
            }
            finally
            {
                _dbSignal.Release();
            }
        }
示例#12
0
        public TAG_INFO GetTags()
        {
            PlaybackSession session = _playbackProcessor.PlaybackSession;

            if (session == null)
            {
                return(null);
            }
            ITagSource source = session.CurrentInputSource as ITagSource;

            if (source == null)
            {
                return(null);
            }
            return(source.Tags);
        }
示例#13
0
        public void Dispose()
        {
            if (disposed)
            {
                return;
            }

            if (MediaList != null)
            {
                MediaList.Dispose();
                MediaList = null; // Setter triggers vector unsubscribe logic
            }

            PlaybackSession.Dispose();

            disposed = true;
        }
        private void PlaybackStartedHandler(PlaybackSession session, IEnumerable <FullTrack> tracks)
        {
            var fullTracks = tracks.Take(2).ToArray();

            _logger.Log(LogLevel.Information, "New playback started for session: {session}. New track: {trackInfo}", JsonConvert.SerializeObject(session), JsonConvert.SerializeObject(fullTracks.First()));

            // To set the channel, run "@Bot set-update-channel" in the channel you want updates posted to
            var success = ulong.TryParse(session.UpdateChannelDiscordId, out ulong id);

            if (success)
            {
                var updateChannel   = _discordClient.GetChannel(id) as IMessageChannel;
                var currentPlaylist = _spotifyApi.GetPlaylist(null, session.SpotifyPlaylistId);

                updateChannel?.SendMessageAsync("", false, EmbedFactory.NowPlaying(session.DiscordVoiceChannelId, fullTracks[0], fullTracks[1], currentPlaylist)).Wait();
            }
        }
        async Task PlayAsync(PlaybackSession playbackSession, Uri source, CancellationToken cancellationToken)
        {
            try
            {
                using (playbackSession)
                {
                    _playbackSession = playbackSession;

                    await playbackSession.PlayAsync(source, cancellationToken).ConfigureAwait(false);

                    _playbackSession = null;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("StreamingMediaPlugin.PlayAsync() failed: " + ex.ExtendedMessage());
            }
        }
        public async Task <bool> ChangePlaylist(PlaybackSession session, string newSpotifyPlaylistId)
        {
            session.SpotifyPlaylistId = newSpotifyPlaylistId;
            await _sessionRepo.UpsertSession(session);

            // If there is current playback, remove it's current job, and create a new one
            if (session.CurrentSongPlayback != null)
            {
                var songTracker = session.CurrentSongPlayback;
                songTracker.State = PlaybackState.Skipped;
                await _songRepo.Upsert(songTracker);

                await _jobScheduler.DeleteJob(new JobKey(session.CurrentSongPlayback.JobId));
            }
            await StartNewPlayback(session);

            return(true);
        }
        public async Task UpsertSession(PlaybackSession session)
        {
            if (!session.IsValidForInsert())
            {
                throw new Exception("PlaybackSession is not valid for Upsert");
            }

            await _dbSignal.WaitAsync();

            try
            {
                var entity = await _db.PlaybackSessions
                             .FirstOrDefaultAsync(e => e.DiscordVoiceChannelId == session.DiscordVoiceChannelId);

                if (entity == null)
                {
                    await _db.PlaybackSessions.AddAsync(session);

                    await _db.SaveChangesAsync();
                }
                else
                {
                    _db.PlaybackSessions.Attach(entity);
                    entity = session;
                    await _db.SaveChangesAsync();
                }

                if (session.CurrentSongPlayback != null)
                {
                    // Upsert the session's Id to the CurrentSongPlayback. The relationship is not created automatically
                    var retrievedSession = await GetByDiscordIdAsync(session.DiscordVoiceChannelId);

                    retrievedSession.CurrentSongPlayback.PlaybackSession = retrievedSession;
                    await _playbackRepo.Upsert(retrievedSession.CurrentSongPlayback);
                }

                await _db.SaveChangesAsync();
            }
            finally
            {
                _dbSignal.Release();
            }
        }
示例#18
0
        // Initilize method to uninitialize the members
        public void Uninitialize()
        {
            _description = null;

            // delete the overlaySurface.
            if (null != _overlaySurface)
            {
                _overlaySurface.Visible = false;
                _overlaySurface.Free();
                _overlaySurface = null;
            }

            if (null != _videoSurface)
            {
                _videoSurface = null;
            }

            // remove the Tuning object
            if (null != _tuning)
            {
                _tuning = null;
            }

            // remove the event handler object
            if (null != _eventHandler)
            {
                _eventHandler.UnInitialize();
                _eventHandler = null;
            }

            // delete the session object
            if (null != _session)
            {
                _session = null;
            }

            // delete the AudioStreamSwitcher
            if (null != _audioStreamSwitcher)
            {
                _audioStreamSwitcher.Uninitialize();
                _audioStreamSwitcher = null;
            }
        }
        public async Task <IActionResult> ChangePlaylist(string voiceChannelId, string newSpotifyPlaylistId)
        {
            var session = await _playbackRepo.GetByDiscordIdAsync(voiceChannelId, new PlaybackSessionQueryOptions()
            {
                IncludeCurrentPlayback = true
            });

            var payload = new ChangePlaylistInfo();

            if (session == null)
            {
                var playbackSession = new PlaybackSession()
                {
                    DiscordVoiceChannelId = voiceChannelId,
                    SpotifyPlaylistId     = newSpotifyPlaylistId
                };
                await _playbackRepo.UpsertSession(playbackSession);

                return(Ok(playbackSession));
            }
            else
            {
                payload.PreviousPlaylist = session.SpotifyPlaylistId;
            }

            var result = await _playbackSessionService.ChangePlaylist(session, newSpotifyPlaylistId);

            session = await _playbackRepo.GetByDiscordIdAsync(voiceChannelId, new PlaybackSessionQueryOptions()
            {
                IncludeCurrentPlayback = true
            });

            payload.NewPlaylist = session.SpotifyPlaylistId;
            if (result)
            {
                return(Ok(payload));
            }
            else
            {
                return(StatusCode(500));
            }
        }
示例#20
0
        public async Task Upsert_New()
        {
            using (var context = await EntityFrameworkHelpers.SqlContextAsync())
            {
                var original = new PlaybackSession()
                {
                    Id = "foo",
                    SpotifyPlaylistId     = "spotify-playlist",
                    DiscordVoiceChannelId = "discord-channel-id"
                };

                var songRepo    = new SongPlaybackRepository(new NullLogger <SongPlaybackRepository>(), context);
                var sessionRepo = new PlaybackSessionRepository(new NullLogger <PlaybackSessionRepository>(), context, songRepo);

                await sessionRepo.UpsertSession(original);

                var result = await sessionRepo.GetById("foo");

                Assert.NotNull(result);
                Assert.Equal(result, original);
            }
        }
示例#21
0
        public async Task Upsert_New_WithCurrentPlayback()
        {
            using (var context = await EntityFrameworkHelpers.SqlContextAsync())
            {
                var originalSongTracker = new SongPlaybackTracker()
                {
                    Id             = "foo-song",
                    SpotifySongUri = "test-spotify-song-uri",
                    State          = PlaybackState.Playing,
                    Duration       = TimeSpan.FromSeconds(30),
                    StartedUtc     = DateTime.UtcNow
                };

                var original = new PlaybackSession()
                {
                    Id = "foo",
                    SpotifyPlaylistId     = "spotify-playlist",
                    DiscordVoiceChannelId = "discord-channel-id",
                    CurrentSongPlayback   = originalSongTracker
                };

                var songRepo    = new SongPlaybackRepository(new NullLogger <SongPlaybackRepository>(), context);
                var sessionRepo = new PlaybackSessionRepository(new NullLogger <PlaybackSessionRepository>(), context, songRepo);

                await sessionRepo.UpsertSession(original);

                var result = await sessionRepo.GetById("foo");

                var songResult = await songRepo.GetById("foo-song");

                Assert.NotNull(result);
                Assert.Equal(result, original);
                Assert.NotNull(result.CurrentSongPlayback);
                Assert.Equal(result.CurrentSongPlayback, originalSongTracker);
                Assert.Equal(result, songResult.PlaybackSession);
            }
        }
        async Task PlaybackLoadingAsync(MediaLoadingEventArgs mediaLoadingEventArgs)
        {
            // ReSharper disable once PossiblyMistakenUseOfParamsMethod
            var mediaLoadingCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_unloadCancellationTokenSource.Token);

            var oldTokenSource = Interlocked.Exchange(ref _mediaLoadingCancellationTokenSource, mediaLoadingCancellationTokenSource);

            if (null != oldTokenSource)
            {
                oldTokenSource.CancelDisposeSafe();
            }

            var source = mediaLoadingEventArgs.Source;

            if (null == source)
            {
                return;
            }

            MediaPlayerDeferral deferral = null;

            try
            {
                deferral = mediaLoadingEventArgs.DeferrableOperation.GetDeferral();

                if (deferral.CancellationToken.IsCancellationRequested)
                {
                    return;
                }

                using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(deferral.CancellationToken, mediaLoadingCancellationTokenSource.Token))
                {
                    var cancellationToken = linkedTokenSource.Token;

                    PlaybackSession playbackSession;

                    using (await _asyncLock.LockAsync(cancellationToken).ConfigureAwait(false))
                    {
                        playbackSession = _playbackSession;

                        if (null != playbackSession)
                        {
                            await playbackSession.StopAsync(cancellationToken);

                            playbackSession = _playbackSession;

                            if (null != playbackSession)
                            {
                                Debug.WriteLine("StreamingMediaPlugin MediaLoading non-null _playbackSession");

                                return;
                            }
                        }

                        if (string.Equals(source.Scheme, "stop", StringComparison.OrdinalIgnoreCase))
                        {
                            Debug.WriteLine("StreamingMediaPlugin MediaLoading stop");
                            return;
                        }

                        var passThrough = StreamingMediaSettings.Parameters.IsPassThrough;

                        if (null != passThrough)
                        {
                            if (passThrough(source))
                            {
                                Debug.WriteLine("StreamingMediaPlugin.PlayerOnMediaLoading() passing through " + source);

                                deferral.Complete();
                                deferral = null;

                                return;
                            }
                        }

                        playbackSession = new PlaybackSession(InitializeMediaStream());

                        var playTask = PlayAsync(playbackSession, source, cancellationToken);

                        TaskCollector.Default.Add(playTask, "StreamingMediaPlugin MediaLoading playTask");
                    }

                    mediaLoadingEventArgs.MediaStreamSource = await playbackSession.GetMediaSourceAsync(cancellationToken).ConfigureAwait(false);

                    mediaLoadingEventArgs.Source = null;

                    //Debug.WriteLine("StreamingMediaPlugin MediaLoading deferral.Complete()");
                    deferral.Complete();
                    deferral = null;
                }
            }
            catch (OperationCanceledException)
            { }
            catch (Exception ex)
            {
                Debug.WriteLine("StreamingMediaPlugin.PlayerOnMediaLoading() failed: " + ex.Message);
            }
            finally
            {
                if (null != deferral)
                {
                    Debug.WriteLine("StreamingMediaPlugin MediaLoading deferral.Cancel()");
                    deferral.Cancel();
                }
            }
        }
示例#23
0
        async Task PlaybackLoadingAsync(MediaLoadingEventArgs mediaLoadingEventArgs)
        {
            // ReSharper disable once PossiblyMistakenUseOfParamsMethod
            var mediaLoadingCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_unloadCancellationTokenSource.Token);

            var oldTokenSource = Interlocked.Exchange(ref _mediaLoadingCancellationTokenSource, mediaLoadingCancellationTokenSource);

            if (null != oldTokenSource)
                oldTokenSource.CancelDisposeSafe();

            var source = mediaLoadingEventArgs.Source;

            if (null == source)
                return;

            MediaPlayerDeferral deferral = null;

            try
            {
                deferral = mediaLoadingEventArgs.DeferrableOperation.GetDeferral();

                if (deferral.CancellationToken.IsCancellationRequested)
                    return;

                using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(deferral.CancellationToken, mediaLoadingCancellationTokenSource.Token))
                {
                    var cancellationToken = linkedTokenSource.Token;

                    PlaybackSession playbackSession;

                    using (await _asyncLock.LockAsync(cancellationToken).ConfigureAwait(false))
                    {
                        playbackSession = _playbackSession;

                        if (null != playbackSession)
                        {
                            await playbackSession.StopAsync(cancellationToken);

                            playbackSession = _playbackSession;

                            if (null != playbackSession)
                            {
                                Debug.WriteLine("StreamingMediaPlugin MediaLoading non-null _playbackSession");

                                return;
                            }
                        }

                        if (string.Equals(source.Scheme, "stop", StringComparison.OrdinalIgnoreCase))
                        {
                            Debug.WriteLine("StreamingMediaPlugin MediaLoading stop");
                            return;
                        }

                        var passThrough = StreamingMediaSettings.Parameters.IsPassThrough;

                        if (null != passThrough)
                        {
                            if (passThrough(source))
                            {
                                Debug.WriteLine("StreamingMediaPlugin.PlayerOnMediaLoading() passing through " + source);

                                deferral.Complete();
                                deferral = null;

                                return;
                            }
                        }

                        playbackSession = new PlaybackSession(InitializeMediaStream());

                        var playTask = PlayAsync(playbackSession, source, cancellationToken);

                        TaskCollector.Default.Add(playTask, "StreamingMediaPlugin MediaLoading playTask");
                    }

                    mediaLoadingEventArgs.MediaStreamSource = await playbackSession.GetMediaSourceAsync(cancellationToken).ConfigureAwait(false);
                    mediaLoadingEventArgs.Source = null;

                    //Debug.WriteLine("StreamingMediaPlugin MediaLoading deferral.Complete()");
                    deferral.Complete();
                    deferral = null;
                }
            }
            catch (OperationCanceledException)
            { }
            catch (Exception ex)
            {
                Debug.WriteLine("StreamingMediaPlugin.PlayerOnMediaLoading() failed: " + ex.Message);
            }
            finally
            {
                if (null != deferral)
                {
                    Debug.WriteLine("StreamingMediaPlugin MediaLoading deferral.Cancel()");
                    deferral.Cancel();
                }
            }
        }
示例#24
0
        async Task PlayAsync(PlaybackSession playbackSession, Uri source, CancellationToken cancellationToken)
        {
            try
            {
                using (playbackSession)
                {
                    _playbackSession = playbackSession;

                    await playbackSession.PlayAsync(source, cancellationToken).ConfigureAwait(false);

                    _playbackSession = null;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("StreamingMediaPlugin.PlayAsync() failed: " + ex.ExtendedMessage());
            }
        }
 public Task SetTextUpdateChannel(PlaybackSession session, string updateChannelDiscordId)
 {
     session.UpdateChannelDiscordId = updateChannelDiscordId;
     return(_sessionRepo.UpsertSession(session));
 }