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}" })); }
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); } }