protected async Task <bool> GetLibraryUpdateAsync(CancellationToken cancellationToken) { DACPRequest request = new DACPRequest("/update"); request.QueryParameters["revision-number"] = CurrentLibraryUpdateNumber.ToString(); request.QueryParameters["daap-no-disconnect"] = "1"; try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { return(false); } var nodes = DACPNodeDictionary.Parse(response.Nodes); CurrentLibraryUpdateNumber = nodes.GetInt("musr"); } catch { return(false); } return(true); }
protected async Task <bool> UpdateCurrentSongUserRatingAsync() { // Make sure we have all the values we need if (CurrentDatabaseID == 0 || CurrentContainerID == 0 || CurrentItemID == 0 || CurrentAlbumPersistentID == 0) { ClearCurrentSongUserRating(); return(true); } // Make sure this is for the main DB if (!ShowUserRating || MainDatabase == null || CurrentDatabaseID != MainDatabase.ID) { ClearCurrentSongUserRating(); return(true); } // If we're requesting the rating for a new song, clear out the old value if (CurrentItemID != _ratingUpdatedForSongID) { ClearCurrentSongUserRating(); } DACPRequest request = new DACPRequest("/databases/{0}/containers/{1}/items", CurrentDatabaseID, CurrentContainerID); request.QueryParameters["meta"] = "dmap.itemid,dmap.containeritemid,daap.songuserrating"; request.QueryParameters["type"] = "music"; request.QueryParameters["sort"] = "album"; var query = DACPQueryCollection.And(DACPQueryPredicate.Is("daap.songalbumid", CurrentAlbumPersistentID), DACPQueryPredicate.Is("dmap.itemid", CurrentItemID)); request.QueryParameters["query"] = query.ToString(); try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); var mlcl = response.Nodes.First(n => n.Key == "mlcl"); var songNodes = DACPUtility.GetResponseNodes(mlcl.Value); foreach (var songData in songNodes) { var nodes = DACPNodeDictionary.Parse(songData.Value); var id = nodes.GetInt("miid"); if (id != CurrentItemID) { continue; } var rating = nodes.GetByte("asur"); SetCurrentSongUserRatingFromServer(rating); break; } } catch { ClearCurrentSongUserRating(); return(false); } return(true); }
public static IDACPList GetAlphaGroupedDACPList <T>(List <T> items, IEnumerable <DACPNode> headers, bool useGroupMinimums = true) { // Determine whether we need to process the headers bool processHeaders = false; // Make sure headers were returned if (headers != null) { if (useGroupMinimums) { // Only show headers if we have at least 10 items if (items.Count >= 10) { // Also make sure we have more than 1 group headers = headers.ToList(); if (((IList)headers).Count >= 2) { processHeaders = true; } } } else { processHeaders = true; } } // If we're not processing headers, just return the items in a list if (!processHeaders) { return(items.ToDACPList()); } var result = new DACPList <ItemGroup <T> >(true); // Create groups for each letter var groupsByChar = new Dictionary <char, ItemGroup <T> >(AlphaGroupChars.Length); foreach (char c in AlphaGroupChars) { var group = new ItemGroup <T>(c.ToString()); result.Add(group); groupsByChar[c] = group; } // Add the items to their groups foreach (var header in headers.Select(n => DACPNodeDictionary.Parse(n.Value))) { char headerChar = GetKeyChar(header.GetString("mshc")); int skip = header.GetInt("mshi"); int take = header.GetInt("mshn"); groupsByChar[headerChar].AddRange(items.Skip(skip).Take(take)); } return(result); }
protected async Task <bool> GetServerInfoAsync() { DACPRequest request = new DACPRequest("/server-info"); request.IncludeSessionID = false; try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); // Process response ServerVersionString = response.HTTPResponse.Headers.GetValues("DAAP-Server").FirstOrDefault(); var nodes = DACPNodeDictionary.Parse(response.Nodes); // Fixing an issue with Apple TV devices where \0 may be appended to the end of the library name string libraryName = nodes.GetString("minm"); if (!string.IsNullOrEmpty(libraryName)) { libraryName = libraryName.Replace("\0", ""); } LibraryName = libraryName; ServerVersion = nodes.GetInt("aeSV"); ServerDMAPVersion = nodes.GetInt("mpro"); ServerDAAPVersion = nodes.GetInt("apro"); // MAC addresses if (nodes.ContainsKey("msml")) { List <string> macAddresses = new List <string>(); var addressNodes = DACPUtility.GetResponseNodes(nodes["msml"]).Where(n => n.Key == "msma").Select(n => n.Value); foreach (var addressNode in addressNodes) { var address = BitConverter.ToInt64(addressNode, 0); address = address >> 16; macAddresses.Add(address.ToString("X12")); } MACAddresses = macAddresses.ToArray(); } } catch (Exception e) { HandleHTTPException(request, e); return(false); } return(true); }
protected async Task <bool> GetServerCapabilitiesAsync() { DACPRequest request = new DACPRequest("/ctrl-int"); request.IncludeSessionID = false; try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); // Process response var mlcl = DACPUtility.GetResponseNodes(response.Nodes.First(n => n.Key == "mlcl").Value); var nodes = DACPNodeDictionary.Parse(mlcl.First(n => n.Key == "mlit").Value); if (nodes.ContainsKey("ceSX")) { Int64 ceSX = nodes.GetLong("ceSX"); // Bit 0: Supports Play Queue if ((ceSX & (1 << 0)) != 0) { SupportsPlayQueue = true; } // Bit 1: iTunes Radio? Appeared in iTunes 11.1.2 with the iTunes Radio DB. // Apple's Remote for iOS doesn't seem to use this bit to determine whether iTunes Radio is available. // Instead, it looks for an iTunes Radio database and checks whether it has any containers. // Bit 2: Genius Shuffle Enabled/Available if ((ceSX & (1 << 2)) != 0) { SupportsGeniusShuffle = true; } } // Apple TV // TODO: Is this the best way to detect this? IsAppleTV = nodes.GetBool("ceDR"); } catch (Exception e) { HandleHTTPException(request, e); return(false); } return(true); }
protected async Task <bool> UpdateCurrentVolumeLevelAsync() { DACPRequest request = new DACPRequest("/ctrl-int/1/getproperty"); request.QueryParameters["properties"] = "dmcp.volume"; try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); var nodes = DACPNodeDictionary.Parse(response.Nodes); CurrentVolume = (byte)nodes.GetInt("cmvo"); } catch { return(false); } return(true); }
protected async Task <bool> UpdateTrackTimeAsync() { DACPRequest request = new DACPRequest("/ctrl-int/1/getproperty"); request.QueryParameters["properties"] = "dacp.playingtime"; try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); var nodes = DACPNodeDictionary.Parse(response.Nodes); TrackTimeTotal = nodes.GetInt("cast"); TrackTimeRemaining = nodes.GetNullableInt("cant") ?? TrackTimeTotal; } catch { return(false); } return(true); }
protected async Task <ConnectionResult> LoginAsync() { DACPRequest request = new DACPRequest("/login"); request.QueryParameters["pairing-guid"] = "0x" + PairingCode; request.IncludeSessionID = false; try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); // Process response var nodes = DACPNodeDictionary.Parse(response.Nodes); if (!nodes.ContainsKey("mlid")) { return(ConnectionResult.InvalidPIN); } SessionID = nodes.GetInt("mlid"); } catch (DACPRequestException e) { int statusCode = (int)e.Response.StatusCode; if (statusCode >= 500 && statusCode <= 599) { return(ConnectionResult.InvalidPIN); } return(ConnectionResult.ConnectionError); } catch (Exception e) { HandleHTTPException(request, e); return(ConnectionResult.ConnectionError); } return(ConnectionResult.Success); }
public static IEnumerable <T> GetItemsFromNodes <T>(IEnumerable <DACPNode> nodes, Func <DACPNodeDictionary, T> itemGenerator, string listKey = DefaultListKey) { return(GetItemsFromNodes(nodes, d => itemGenerator(DACPNodeDictionary.Parse(d)), listKey)); }
public static IEnumerable <T> GetItemsFromNodes <T>(byte[] data, Func <DACPNodeDictionary, T> itemGenerator, string listKey = DefaultListKey) { return(GetItemsFromNodes(GetResponseNodes(data), d => itemGenerator(DACPNodeDictionary.Parse(d)), listKey)); }
public static IDACPList GetAlphaGroupedDACPList <T>(IEnumerable <DACPNode> nodes, Func <DACPNodeDictionary, T> itemGenerator, out List <T> items, string listKey = DefaultListKey, bool useGroupMinimums = true) { return(GetAlphaGroupedDACPList(nodes, d => itemGenerator(DACPNodeDictionary.Parse(d)), out items, listKey, useGroupMinimums)); }
protected async Task <bool> GetControlPromptUpdateAsync(CancellationToken cancellationToken) { DACPRequest request = new DACPRequest("/controlpromptupdate"); request.QueryParameters["prompt-id"] = _controlPromptUpdateNumber.ToString(); try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { return(false); } _controlPromptUpdateNumber = response.Nodes.First(n => n.Key == "miid").Value.GetInt32Value(); // Parse response // This comes back as a list of string key/value pairs. var nodeDictionary = response.Nodes.Where(n => n.Key == "mdcl").Select(n => DACPNodeDictionary.Parse(n.Value)).ToDictionary(n => n.GetString("cmce"), n => n.GetString("cmcv")); #if DEBUG if (_log.EffectiveLevel <= LogLevel.Trace) { string logMessage = "ControlPromptUpdate Response:\r\n"; foreach (var kvp in nodeDictionary) { logMessage += string.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); } _log.Trace(logMessage); } #endif if (nodeDictionary.ContainsKey("kKeybMsgKey_MessageType")) { switch (nodeDictionary["kKeybMsgKey_MessageType"]) { case "0": // Show keyboard CurrentAppleTVKeyboardTitle = nodeDictionary.GetValueOrDefault("kKeybMsgKey_Title"); CurrentAppleTVKeyboardSubText = nodeDictionary.GetValueOrDefault("kKeybMsgKey_SubText"); CurrentAppleTVKeyboardString = nodeDictionary.GetValueOrDefault("kKeybMsgKey_String"); _appleTVKeyboardSessionID = nodeDictionary["kKeybMsgKey_SessionID"]; _appleTVKeyboardSecureText = (nodeDictionary["kKeybMsgKey_SecureText"] == "1"); _appleTVKeyboardSecureTextCertificate = nodeDictionary.GetValueOrDefault("certificate"); _appleTVKeyboardSecureTextChallenge = nodeDictionary.GetValueOrDefault("challenge"); if (_appleTVKeyboardSecureText) { CurrentAppleTVKeyboardType = AppleTVKeyboardType.Password; } else { CurrentAppleTVKeyboardType = (AppleTVKeyboardType)int.Parse(nodeDictionary["kKeybMsgKey_KeyboardType"]); } IsAppleTVKeyboardVisible = true; break; case "2": // Hide keyboard IsAppleTVKeyboardVisible = false; break; case "5": // Trackpad interface update _appleTVTrackpadPort = int.Parse(nodeDictionary["kKeybMsgKey_String"]) ^ AppleTVEncryptionKey; _appleTVTrackpadKey = BitUtility.NetworkToHostOrder(int.Parse(nodeDictionary["kKeybMsgKey_SubText"]) ^ AppleTVEncryptionKey); _log.Info("Apple TV virtual trackpad parameters updated: Encryption key: {0:X8} Port: {1}", _appleTVTrackpadKey, _appleTVTrackpadPort); break; } } } catch { return(false); } return(true); }
protected async Task <bool> GetPlayStatusUpdateAsync(CancellationToken cancellationToken) { // Do not pass the cancellation token to the HTTP request since cancelling a request will cause iTunes to close the current session. DACPRequest request = new DACPRequest("/ctrl-int/1/playstatusupdate"); request.QueryParameters["revision-number"] = _playStatusRevisionNumber.ToString(); try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); // Do we still need to process this response? if (cancellationToken.IsCancellationRequested) { return(false); } // Process response ThreadUtility.RunOnUIThread(() => { timerTrackTimeUpdate.Stop(); }); var nodes = DACPNodeDictionary.Parse(response.Nodes); _playStatusRevisionNumber = nodes.GetInt("cmsr"); int oldSongID = CurrentItemID; if (nodes.ContainsKey("canp")) { // Current song and container IDs byte[] value = nodes["canp"]; byte[] dbID = { value[0], value[1], value[2], value[3] }; byte[] containerID = { value[4], value[5], value[6], value[7] }; byte[] containerItemID = { value[8], value[9], value[10], value[11] }; byte[] itemID = { value[12], value[13], value[14], value[15] }; CurrentDatabaseID = dbID.GetInt32Value(); CurrentContainerID = containerID.GetInt32Value(); CurrentContainerItemID = containerItemID.GetInt32Value(); CurrentItemID = itemID.GetInt32Value(); } else { CurrentDatabaseID = 0; CurrentContainerID = 0; CurrentContainerItemID = 0; CurrentItemID = 0; } CurrentSongName = nodes.GetString("cann"); CurrentArtist = nodes.GetString("cana"); CurrentAlbum = nodes.GetString("canl"); CurrentAlbumPersistentID = (UInt64)nodes.GetLong("asai"); PlayState = (PlayStates)nodes.GetByte("caps"); // Shuffle int caas = nodes.GetInt("caas"); IsShuffleAvailable = (caas & (1 << 1)) != 0; ShuffleState = nodes.GetBool("cash"); // Repeat int caar = nodes.GetInt("caar"); IsRepeatOneAvailable = (caar & (1 << 1)) != 0; IsRepeatAllAvailable = (caar & (1 << 2)) != 0; RepeatState = (RepeatStates)nodes.GetByte("carp"); CurrentMediaKind = nodes.GetInt("cmmk"); ShowUserRating = nodes.GetBool("casu"); // Track length (ms) TrackTimeTotal = nodes.GetInt("cast"); // Remaining track length (ms) TrackTimeRemaining = nodes.GetNullableInt("cant") ?? TrackTimeTotal; // dacp.visualizer VisualizerActive = nodes.GetBool("cavs"); // dacp.visualizerenabled VisualizerAvailable = nodes.GetBool("cave"); // dacp.fullscreen FullScreenModeActive = nodes.GetBool("cafs"); // dacp.fullscreenenabled FullScreenModeAvailable = nodes.GetBool("cafe"); // iTunes Radio if (iTunesRadioDatabase != null && iTunesRadioDatabase.ID == CurrentDatabaseID) { IsCurrentlyPlayingiTunesRadio = true; CurrentiTunesRadioStationName = nodes.GetString("ceNR"); // caks = 1 when the next button is disabled, and 2 when it's enabled IsiTunesRadioNextButtonEnabled = (nodes.GetByte("caks") == 2); // "aelb" indicates whether the star button (iTunes Radio menu) should be enabled, but this only seems to be set to true // when connected via Home Sharing. This parameter is missing when an ad is playing, so use this to determine whether // the menu should be enabled. IsiTunesRadioMenuEnabled = nodes.ContainsKey("aelb"); IsiTunesRadioSongFavorited = (nodes.GetByte("aels") == 2); } else { IsCurrentlyPlayingiTunesRadio = false; } if (IsCurrentlyPlayingiTunesRadio) { var caks = nodes.GetByte("caks"); IsiTunesRadioNextButtonEnabled = !(caks == 1); } if (!nodes.ContainsKey("casc") || nodes.GetBool("casc") == true) { IsPlayPositionBarEnabled = true; } else { IsPlayPositionBarEnabled = false; } // Genius Shuffle IsCurrentlyPlayingGeniusShuffle = nodes.GetBool("ceGs"); // There are two other nodes related to Genius Shuffle, "ceGS" and "aeGs" (currently unknown) // If the song ID changed, refresh the album art if (oldSongID != CurrentItemID) { PropertyChanged.RaiseOnUIThread(this, "CurrentAlbumArtURL"); } ThreadUtility.RunOnUIThread(() => { if (PlayState == PlayStates.Playing) { timerTrackTimeUpdate.Start(); } else if (PlayState == PlayStates.FastForward || PlayState == PlayStates.Rewind) { BeginRepeatedTrackTimeRequest(); } }); var volumeTask = UpdateCurrentVolumeLevelAsync(); var userRatingTask = UpdateCurrentSongUserRatingAsync(); var playQueueTask = UpdatePlayQueueContentsAsync(); Task[] tasks = new[] { volumeTask, userRatingTask, playQueueTask }; #if WP7 await TaskEx.WhenAll(tasks).ConfigureAwait(false); #else await Task.WhenAll(tasks).ConfigureAwait(false); #endif SubmitGetSpeakersRequest(); } catch { return(false); } return(true); }
internal Task <IDACPList> GetAlphaGroupedListAsync <T>(DACPRequest request, Func <DACPNodeDictionary, T> itemGenerator, string listKey = DACPUtility.DefaultListKey) { return(GetAlphaGroupedListAsync(request, b => itemGenerator(DACPNodeDictionary.Parse(b)), listKey)); }