Example #1
0
        public async Task <IActionResult> Room(IndexModel room)
        {
            room.Error = "";

            var username = HttpContext.User.Claims.GetSpotifyUsername();

            // turn off shuffle and repeat, best effort...
            try
            {
                var api = await _spotifyAccessService.TryGetMemberApi(username);

                var devices = await api.GetDevicesAsync();

                devices.ThrowOnError(nameof(api.GetDevices));

                if (!devices.Devices.Any())
                {
                    throw new Exception("No devices available to set shuffle/repeat on!");
                }

                var device = devices.Devices.PickPreferredDevice();

                _devicePersistenceService.SetDeviceState(username, device.Id);

                var repeatResult = await api.SetRepeatModeAsync(RepeatState.Off, device.Id);

                repeatResult.ThrowOnError(nameof(api.SetRepeatModeAsync));

                var shuffleResult = await api.SetShuffleAsync(false, device.Id);

                shuffleResult.ThrowOnError(nameof(api.SetShuffleAsync));

                _logger.Log(LogLevel.Information, "Turned off shuffle and repeat for {Username}", username);
            }
            catch (Exception e)
            {
                // oh well
                Debug.WriteLine(e);
                _logger.Log(LogLevel.Warning, "Failed to turn off shuffle and/or repeat for {Username} upon entering room because {@Exception}", username, e);

                // redirect to index
                room.Error = "Couldn't find any Spotify clients to use! Make sure you have one online before trying to join a room!";
                if (e.ToString().Contains("Restriction violated"))
                {
                    room.Error = "Something seems to went wrong on Spotify's end while setting up playback for you - please try again!";
                }
                return(View(nameof(Index), room));
            }

            // prev room?
            var alreadyInRoom = _roomService.TryGetRoomForUsername(username, out var prevRoom);

            if (alreadyInRoom && room.RoomName != prevRoom.RoomId)
            {
                // if it doesn't match leave the prev one
                prevRoom.MemberLeave(prevRoom.Members.First(x => x.UserName == username));
            }

            // sanitize room name
            var rgx = new Regex("[^a-zA-Z-]");

            if (string.IsNullOrWhiteSpace(room.RoomName))
            {
                room.RoomName = NaughtyRoomName;
            }
            var sanitizedRoomName = rgx.Replace(room.RoomName.Replace(" ", "-"), string.Empty);

            if (string.IsNullOrWhiteSpace(sanitizedRoomName) || sanitizedRoomName.Length < 3)
            {
                // hehe
                sanitizedRoomName = NaughtyRoomName;
            }
            room.RoomName = sanitizedRoomName;

            var party = _roomService.EnsureRoom(room.RoomName);

            _logger.Log(LogLevel.Information, "{Page} loaded for {Username}", "Room", username);

            var member = new RoomMember(username, HttpContext.User.Claims.GetSpotifyFriendlyName(), room.PlaylistId);

            party.MemberJoin(member);

            return(View(new RoomModel
            {
                RoomName = room.RoomName,
                UserName = username,
                QueuePlaylistLink = $"spotify:playlist:{room.PlaylistId}"
            }));
        }
Example #2
0
        private async Task PlaySong(RoomMember member, FullTrack song, int positionMs = 0, bool canRetry = true)
        {
            try
            {
                var api = await _spotifyAccessService.TryGetMemberApi(member.UserName);

                var devices = await api.GetDevicesAsync();

                devices.ThrowOnError(nameof(api.GetDevices));

                if (!devices.Devices.Any())
                {
                    throw new Exception("No devices available to play on!");
                }

                var activeDevice = devices.Devices.FirstOrDefault(x => x.IsActive);

                var hasShouldBeActive = _devicePersistenceService.TryGetActiveDeviceId(member.UserName, out var shouldBeActiveDeviceId);

                if (activeDevice != null && activeDevice.Id != shouldBeActiveDeviceId)
                {
                    // set if changed
                    _devicePersistenceService.SetDeviceState(member.UserName, activeDevice.Id);
                }

                // active device was somehow lost, if it's still in the list then reactivate it
                if (activeDevice == null && hasShouldBeActive)
                {
                    activeDevice = devices.Devices.FirstOrDefault(x => x.Id == shouldBeActiveDeviceId);
                }

                if (activeDevice == null)
                {
                    _devicePersistenceService.CleanDeviceState(member.UserName);
                    throw new Exception("No active device found to play music on.");
                }

                var resume = await api.ResumePlaybackAsync(deviceId : activeDevice.Id, uris : new List <string> {
                    song.Uri
                }, offset : 0, positionMs : positionMs);

                resume.ThrowOnError(nameof(api.ResumePlaybackAsync));

                // reset failure count if playback started successfully
                if (member.PlayFailureCount > 0)
                {
                    member.PlayFailureCount = 0;

                    _logger.Log(LogLevel.Information, "Reset play failure count for {Username}", member.UserName);
                }
            }
            catch (Exception e)
            {
                _logger.Log(LogLevel.Warning, "Failed to play song for {Username} because {@Exception}", member.UserName, e);
                OnRoomNotification?.Invoke(this, new RoomNotification
                {
                    Category = RoomNotificationCategory.Error,
                    Message  = $"Failed to play song",
                    TargetId = member.ConnectionId
                });

                if (e.Message.Contains("HTTP 5") && canRetry)
                {
                    // if it's a server side error let's add it to the retry queue
                    async Task RetryTask()
                    {
                        // make sure user hasn't left in the last room-cycle
                        if (!_members.Contains(member))
                        {
                            return;
                        }
                        // try starting song again
                        var left = _handledUntil.ToUnixTimeMilliseconds() - DateTimeOffset.Now.ToUnixTimeMilliseconds();

                        await PlaySong(member, _currentTrack, (int)(_currentTrack.DurationMs - left), false);
                    }

                    _logger.Log(LogLevel.Information, "Added retry task for {UserName}", member.UserName);

                    _roomRetries.Add(RetryTask);
                }
                else
                {
                    // not a server side error, increment member failure count
                    member.PlayFailureCount += 1;

                    _logger.Log(LogLevel.Information, "Incremented play failure count to {PlayFailureCount} for {Username}", member.PlayFailureCount, member.UserName);
                }

                Debug.WriteLine(e);
            }
        }