コード例 #1
0
        /// <inheritdoc />
        public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
        {
            var user = _userManager.GetUserById(session.UserId);

            if (user.SyncPlayAccess == SyncPlayAccess.None)
            {
                _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id);

                var error = new GroupUpdate <string>()
                {
                    Type = GroupUpdateType.JoinGroupDenied
                };
                _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
                return;
            }

            lock (_groupsLock)
            {
                _sessionToGroupMap.TryGetValue(session.Id, out var group);

                if (group == null)
                {
                    _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id);

                    var error = new GroupUpdate <string>()
                    {
                        Type = GroupUpdateType.NotInGroup
                    };
                    _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
                    return;
                }

                group.HandleRequest(session, request, cancellationToken);
            }
        }
コード例 #2
0
        /// <summary>
        /// Handles a play action requested by a session.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="request">The play action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
        {
            if (_group.IsPaused)
            {
                // Pick a suitable time that accounts for latency
                var delay = _group.GetHighestPing() * 2;
                delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;

                // Unpause group and set starting point in future
                // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position)
                // The added delay does not guarantee, of course, that the command will be received in time
                // Playback synchronization will mainly happen client side
                _group.IsPaused     = false;
                _group.LastActivity = DateTime.UtcNow.AddMilliseconds(
                    delay
                    );

                var command = NewSyncPlayCommand(SendCommandType.Play);
                SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
            }
            else
            {
                // Client got lost, sending current state
                var command = NewSyncPlayCommand(SendCommandType.Play);
                SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
            }
        }
コード例 #3
0
        /// <summary>
        /// Handles a buffering action requested by a session.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="request">The buffering action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
        {
            if (!_group.IsPaused)
            {
                // Pause group and compute the media playback position
                _group.IsPaused = true;
                var currentTime = DateTime.UtcNow;
                var elapsedTime = currentTime - _group.LastActivity;
                _group.LastActivity   = currentTime;
                _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0;

                _group.SetBuffering(session, true);

                // Send pause command to all non-buffering sessions
                var command = NewSyncPlayCommand(SendCommandType.Pause);
                SendCommand(session, BroadcastType.AllReady, command, cancellationToken);

                var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.GroupWait, session.UserName);
                SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
            }
            else
            {
                // Client got lost, sending current state
                var command = NewSyncPlayCommand(SendCommandType.Pause);
                SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
            }
        }
コード例 #4
0
        /// <inheritdoc />
        public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
        {
            // The server's job is to mantain a consistent state to which clients refer to,
            // as also to notify clients of state changes.
            // The actual syncing of media playback happens client side.
            // Clients are aware of the server's time and use it to sync.
            switch (request.Type)
            {
            case PlaybackRequestType.Play:
                HandlePlayRequest(session, request, cancellationToken);
                break;

            case PlaybackRequestType.Pause:
                HandlePauseRequest(session, request, cancellationToken);
                break;

            case PlaybackRequestType.Seek:
                HandleSeekRequest(session, request, cancellationToken);
                break;

            case PlaybackRequestType.Buffering:
                HandleBufferingRequest(session, request, cancellationToken);
                break;

            case PlaybackRequestType.BufferingDone:
                HandleBufferingDoneRequest(session, request, cancellationToken);
                break;

            case PlaybackRequestType.UpdatePing:
                HandlePingUpdateRequest(session, request);
                break;
            }
        }
コード例 #5
0
        public async Task ExecuteAsync(QueryResult result)
        {
            if (!(result is IPlayableResource playable))
            {
                return;
            }

            var req = new PlaybackRequest();

            if (string.IsNullOrEmpty(playable.ContextUri))
            {
                req.Uris = playable.ResourceUris;
            }
            else
            {
                req.ContextUri = playable.ContextUri;
                if (playable.ResourceUris == null)
                {
                    req.Offset = new PositionOffset(1);
                }
                else
                {
                    req.Offset = new UriOffset(playable.ResourceUris.First());
                }
            }

            await _spotifyClient.PlayAsync(req);
        }
コード例 #6
0
        /// <summary>
        /// Handles a buffering-done action requested by a session.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="request">The buffering-done action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
        {
            if (_group.IsPaused)
            {
                _group.SetBuffering(session, false);

                var requestTicks = SanitizePositionTicks(request.PositionTicks);

                var when           = request.When ?? DateTime.UtcNow;
                var currentTime    = DateTime.UtcNow;
                var elapsedTime    = currentTime - when;
                var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime;
                var delay          = _group.PositionTicks - clientPosition.Ticks;

                if (_group.IsBuffering())
                {
                    // Others are still buffering, tell this client to pause when ready
                    var command     = NewSyncPlayCommand(SendCommandType.Pause);
                    var pauseAtTime = currentTime.AddMilliseconds(delay);
                    command.When = DateToUTCString(pauseAtTime);
                    SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
                }
                else
                {
                    // Let other clients resume as soon as the buffering client catches up
                    _group.IsPaused = false;

                    if (delay > _group.GetHighestPing() * 2)
                    {
                        // Client that was buffering is recovering, notifying others to resume
                        _group.LastActivity = currentTime.AddMilliseconds(
                            delay
                            );
                        var command = NewSyncPlayCommand(SendCommandType.Play);
                        SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken);
                    }
                    else
                    {
                        // Client, that was buffering, resumed playback but did not update others in time
                        delay = _group.GetHighestPing() * 2;
                        delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;

                        _group.LastActivity = currentTime.AddMilliseconds(
                            delay
                            );

                        var command = NewSyncPlayCommand(SendCommandType.Play);
                        SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
                    }
                }
            }
            else
            {
                // Group was not waiting, make sure client has latest state
                var command = NewSyncPlayCommand(SendCommandType.Play);
                SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
            }
        }
コード例 #7
0
ファイル: SyncPlayService.cs プロジェクト: gut5/jellyfin
        /// <summary>
        /// Handles the specified request.
        /// </summary>
        /// <param name="request">The request.</param>
        public void Post(SyncPlayPause request)
        {
            var currentSession  = GetSession(_sessionContext);
            var syncPlayRequest = new PlaybackRequest()
            {
                Type = PlaybackRequestType.Pause
            };

            _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
        }
コード例 #8
0
        public ActionResult SyncPlayPause()
        {
            var currentSession  = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
            var syncPlayRequest = new PlaybackRequest()
            {
                Type = PlaybackRequestType.Pause
            };

            _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
            return(NoContent());
        }
コード例 #9
0
ファイル: SyncPlayService.cs プロジェクト: gut5/jellyfin
        /// <summary>
        /// Handles the specified request.
        /// </summary>
        /// <param name="request">The request.</param>
        public void Post(SyncPlayPing request)
        {
            var currentSession  = GetSession(_sessionContext);
            var syncPlayRequest = new PlaybackRequest()
            {
                Type = PlaybackRequestType.UpdatePing,
                Ping = Convert.ToInt64(request.Ping)
            };

            _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
        }
コード例 #10
0
ファイル: SyncPlayService.cs プロジェクト: gut5/jellyfin
        /// <summary>
        /// Handles the specified request.
        /// </summary>
        /// <param name="request">The request.</param>
        public void Post(SyncPlaySeek request)
        {
            var currentSession  = GetSession(_sessionContext);
            var syncPlayRequest = new PlaybackRequest()
            {
                Type          = PlaybackRequestType.Seek,
                PositionTicks = request.PositionTicks
            };

            _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
        }
コード例 #11
0
        public ActionResult SyncPlayPing([FromQuery] double ping)
        {
            var currentSession  = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
            var syncPlayRequest = new PlaybackRequest()
            {
                Type = PlaybackRequestType.Ping,
                Ping = Convert.ToInt64(ping)
            };

            _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
            return(NoContent());
        }
コード例 #12
0
        public ActionResult SyncPlaySeek([FromQuery] long positionTicks)
        {
            var currentSession  = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
            var syncPlayRequest = new PlaybackRequest()
            {
                Type          = PlaybackRequestType.Seek,
                PositionTicks = positionTicks
            };

            _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
            return(NoContent());
        }
コード例 #13
0
ファイル: SyncPlayService.cs プロジェクト: gut5/jellyfin
        /// <summary>
        /// Handles the specified request.
        /// </summary>
        /// <param name="request">The request.</param>
        public void Post(SyncPlayBuffering request)
        {
            var currentSession  = GetSession(_sessionContext);
            var syncPlayRequest = new PlaybackRequest()
            {
                Type          = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering,
                When          = DateTime.Parse(request.When),
                PositionTicks = request.PositionTicks
            };

            _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
        }
コード例 #14
0
        public ActionResult SyncPlayBuffering([FromQuery] DateTime when, [FromQuery] long positionTicks, [FromQuery] bool bufferingDone)
        {
            var currentSession  = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
            var syncPlayRequest = new PlaybackRequest()
            {
                Type          = bufferingDone ? PlaybackRequestType.Ready : PlaybackRequestType.Buffer,
                When          = when,
                PositionTicks = positionTicks
            };

            _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
            return(NoContent());
        }
コード例 #15
0
        /// <summary>
        /// Handles a seek action requested by a session.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="request">The seek action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
        {
            // Sanitize PositionTicks
            var ticks = SanitizePositionTicks(request.PositionTicks);

            // Pause and seek
            _group.IsPaused      = true;
            _group.PositionTicks = ticks;
            _group.LastActivity  = DateTime.UtcNow;

            var command = NewSyncPlayCommand(SendCommandType.Seek);

            SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
        }
コード例 #16
0
        /// <summary>
        /// Handles a pause action requested by a session.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="request">The pause action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
        {
            if (!_group.IsPaused)
            {
                // Pause group and compute the media playback position
                _group.IsPaused = true;
                var currentTime = DateTime.UtcNow;
                var elapsedTime = currentTime - _group.LastActivity;
                _group.LastActivity = currentTime;
                // Seek only if playback actually started
                // (a pause request may be issued during the delay added to account for latency)
                _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0;

                var command = NewSyncPlayCommand(SendCommandType.Pause);
                SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
            }
            else
            {
                // Client got lost, sending current state
                var command = NewSyncPlayCommand(SendCommandType.Pause);
                SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
            }
        }
コード例 #17
0
ファイル: SpotifyClient.cs プロジェクト: pardahlman/wrido
        public async Task <OperationResult> PlayAsync(PlaybackRequest request, CancellationToken ct = default)
        {
            var requestUrl = $"{ApiBaseUrl}/me/player/play{ _queryParameterBuilder.Build(request)}";

            var response = await _httpClient.PutAsync(requestUrl, new JsonContent(request, _serializer), ct);

            switch (response.StatusCode)
            {
            case HttpStatusCode.NoContent: return(OperationResult.Success);

            case HttpStatusCode.Accepted: return(OperationResult.DeviceUnavailable);

            case HttpStatusCode.NotFound: return(OperationResult.DeviceNotFound);

            case HttpStatusCode.Forbidden: return(OperationResult.NonPremiumUser);

            case HttpStatusCode.BadRequest:
                var error = await DeserializeBodyAsync <UnsuccessfulOperation>(response);

                throw new SpotifyException("Unable to authenticate", error.Error);

            default: return(OperationResult.Unknown);
            }
        }
コード例 #18
0
 /// <summary>
 /// Updates ping of a session.
 /// </summary>
 /// <param name="session">The session.</param>
 /// <param name="request">The update.</param>
 private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
 {
     // Collected pings are used to account for network latency when unpausing playback
     _group.UpdatePing(session, request.Ping ?? _group.DefaulPing);
 }
コード例 #19
0
 public string Build(PlaybackRequest request)
 {
     return(string.IsNullOrEmpty(request?.DeviceId)
 ? string.Empty
 : $"?device_id={request?.DeviceId}");
 }
コード例 #20
0
        /// <summary>
        /// Get Playback Start Request
        /// </summary>
        /// <param name="playbackStartType">Playlist Start Type</param>
        /// <param name="id">Spotify Id</param>
        /// <param name="position">(Optional) Indicates from what position to start playback. Must be a positive number. Passing in a position that is greater than the length of the track will cause the player to start playing the next song.</param>
        /// <param name="offsetId">(Optional) Only available for PlaybackStartType.Album, PlaybackStartType.Artist and PlaybackStartType.Playlist. Only use either Position or OffsetId. Offset Id indicates from where in the context playback should start.</param>
        /// <returns>Playback Request</returns>
        /// <exception cref="ArgumentException">Thrown when Position and OffsetId are Provided</exception>
        public static PlaybackRequest GetPlaybackStartRequest(
            PlaybackStartType playbackStartType,
            string id,
            int?position    = null,
            string offsetId = null)
        {
            PlaybackRequest playbackRequest = new PlaybackRequest();

            switch (playbackStartType)
            {
            case PlaybackStartType.Track:
                playbackRequest.Position = position;
                playbackRequest.Uris     = new List <string> {
                    GetTrackUri(id)
                };
                break;

            case PlaybackStartType.Episode:
                playbackRequest.Position = position;
                playbackRequest.Uris     = new List <string> {
                    GetEpisodeUri(id)
                };
                break;

            case PlaybackStartType.Album:
                if (offsetId != null && position != null)
                {
                    throw new ArgumentException($"Set {nameof(offsetId)} or {nameof(position)}");
                }
                if (position != null && offsetId == null)
                {
                    playbackRequest.Offset = new PositionRequest()
                    {
                        Position = position
                    }
                }
                ;
                if (offsetId != null && position == null)
                {
                    playbackRequest.Offset = new UriRequest {
                        Uri = GetTrackUri(offsetId)
                    }
                }
                ;
                playbackRequest.ContextUri = GetAlbumUri(id);
                break;

            case PlaybackStartType.Artist:
                if (offsetId != null && position != null)
                {
                    throw new ArgumentException($"Set {nameof(offsetId)} or {nameof(position)}");
                }
                if (position != null && offsetId == null)
                {
                    playbackRequest.Offset = new PositionRequest()
                    {
                        Position = position
                    }
                }
                ;
                if (offsetId != null && position == null)
                {
                    playbackRequest.Offset = new UriRequest {
                        Uri = GetTrackUri(offsetId)
                    }
                }
                ;
                playbackRequest.ContextUri = GetArtistUri(id);
                break;

            case PlaybackStartType.Playlist:
                if (offsetId != null && position != null)
                {
                    throw new ArgumentException($"Set {nameof(offsetId)} or {nameof(position)}");
                }
                if (position != null && offsetId == null)
                {
                    playbackRequest.Offset = new PositionRequest()
                    {
                        Position = position
                    }
                }
                ;
                if (offsetId != null && position == null)
                {
                    playbackRequest.Offset = new UriRequest {
                        Uri = GetTrackUri(offsetId)
                    }
                }
                ;
                playbackRequest.ContextUri = GetPlaylistUri(id);
                break;

            case PlaybackStartType.Show:
                if (offsetId != null && position != null)
                {
                    throw new ArgumentException($"Set {nameof(offsetId)} or {nameof(position)}");
                }
                if (position != null && offsetId == null)
                {
                    playbackRequest.Offset = new PositionRequest()
                    {
                        Position = position
                    }
                }
                ;
                if (offsetId != null && position == null)
                {
                    playbackRequest.Offset = new UriRequest {
                        Uri = GetEpisodeUri(offsetId)
                    }
                }
                ;
                playbackRequest.ContextUri = GetShowUri(id);
                break;
            }
            return(playbackRequest);
        }