internal async Task <DACPResponse> SubmitRequestAsync(DACPRequest request) { if (request.IncludeSessionID) { request.QueryParameters["session-id"] = SessionID.ToString(); } string uri = request.GetURI(); _log.Info("Submitting request for: " + uri); HttpResponseMessage response = await HttpClient.PostAsync(uri, request.HttpContent, request.CancellationToken).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { _log.Info("Invalid response ({0}) for: {1}", response.StatusCode, uri); throw new DACPRequestException(response); } byte[] data = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); _log.Info("Received response for: " + uri); // Get the content of the first node IEnumerable <DACPNode> nodes = null; if (data.Length > 0) { data = DACPUtility.GetResponseNodes(data, true).First().Value; nodes = DACPUtility.GetResponseNodes(data); } return(new DACPResponse(response, nodes)); }
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); }
internal async Task <List <T> > GetListAsync <T>(DACPRequest request, Func <DACPNodeDictionary, T> itemGenerator, string listKey = DACPUtility.DefaultListKey) { try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); return(DACPUtility.GetItemsFromNodes(response.Nodes, itemGenerator, listKey).ToList()); } catch (Exception) { return(new List <T>()); } }
protected async Task <bool> SetVolumeLevelAsync(int value) { DACPRequest request = new DACPRequest("/ctrl-int/1/setproperty"); request.QueryParameters["dmcp.volume"] = value.ToString(); try { await SubmitRequestAsync(request).ConfigureAwait(false); } catch { return(false); } return(true); }
internal async Task <IDACPList> GetAlphaGroupedListAsync <T>(DACPRequest request, Func <byte[], T> itemGenerator, string listKey = DACPUtility.DefaultListKey) { try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); return(DACPUtility.GetAlphaGroupedDACPList(response.Nodes, itemGenerator, listKey)); } catch (Exception) { return(new DACPList <T>(false)); } }
public async Task <bool> Play() { DACPRequest request = new DACPRequest("/ctrl-int/1/playqueue-edit"); request.QueryParameters["command"] = "playnow"; request.QueryParameters["index"] = QueueIndex.ToString(); try { await Server.SubmitRequestAsync(request).ConfigureAwait(false); } catch { return(false); } return(true); }
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> SetUserRatingAsync(int rating, int songID) { DACPRequest request = new DACPRequest("/ctrl-int/1/setproperty"); request.QueryParameters["dacp.userrating"] = rating.ToString(); request.QueryParameters["database-spec"] = DACPQueryPredicate.Is("dmap.persistentid", "0x" + CurrentDatabaseID.ToString("x16")).ToString(); request.QueryParameters["item-spec"] = DACPQueryPredicate.Is("dmap.itemid", "0x" + songID.ToString("x")).ToString(); try { await SubmitRequestAsync(request).ConfigureAwait(false); } catch { return(false); } return(true); }
public async Task <bool> SendGeniusShuffleCommandAsync() { if (!SupportsGeniusShuffle) { return(false); } DACPRequest request = new DACPRequest("/ctrl-int/1/genius-shuffle"); // Apple's Remote seems to always set "span" to "$Q" request.QueryParameters["span"] = "$Q"; try { await SubmitRequestAsync(request); } catch { 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); }
public async Task <bool> SendiTunesRadioNeverPlayThisSongAsync() { if (!IsCurrentlyPlayingiTunesRadio) { return(false); } DACPRequest request = new DACPRequest("/ctrl-int/1/setproperty"); request.QueryParameters["com.apple.itunes.liked-state"] = "3"; request.QueryParameters["database-spec"] = DACPQueryPredicate.Is("dmap.itemid", "0x" + CurrentDatabaseID.ToString("x")).ToString(); request.QueryParameters["item-spec"] = DACPQueryPredicate.Is("dmap.itemid", "0x" + CurrentItemID.ToString("x")).ToString(); try { await SubmitRequestAsync(request); } 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); }
/// <summary> /// Requests an update for the keyboard state and session ID. /// </summary> private async Task <bool> RequestAppleTVKeyboardInfoUpdateAsync() { List <byte> contentBytes = new List <byte>(); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmcc", "0")); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmbe", "PromptResendReq")); ByteArrayContent content = new ByteArrayContent(contentBytes.ToArray()); DACPRequest request = new DACPRequest("/ctrl-int/1/controlpromptentry"); request.HttpContent = content; try { await SubmitRequestAsync(request).ConfigureAwait(false); } catch { return(false); } return(true); }
protected async Task <bool> UpdatePlayQueueContentsAsync() { if (!SupportsPlayQueue) { return(false); } DACPRequest request = new DACPRequest("/ctrl-int/1/playqueue-contents"); request.QueryParameters["span"] = "50"; try { var response = await SubmitRequestAsync(request).ConfigureAwait(false); HandlePlayQueueResponse(response.Nodes); } catch { return(false); } return(true); }
/// <summary> /// Requests an update for the virtual trackpad connection parameters. /// </summary> private async Task <bool> RequestAppleTVTrackpadInfoUpdateAsync() { List <byte> contentBytes = new List <byte>(); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmcc", "0")); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmbe", "DRPortInfoRequest")); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmte", string.Format("{0},0x{1}", AppleTVEncryptionKey, PairingCode))); ByteArrayContent content = new ByteArrayContent(contentBytes.ToArray()); DACPRequest request = new DACPRequest("/ctrl-int/1/controlpromptentry"); request.HttpContent = content; try { await SubmitRequestAsync(request).ConfigureAwait(false); } 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); }
private async Task <bool> SendAppleTVKeyboardStringUpdateCommandAsync(string value, bool done) { string command = (done) ? "PromptDone" : "PromptUpdate"; List <byte> contentBytes = new List <byte>(); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmcc", _appleTVKeyboardSessionID)); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmbe", command)); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmte", value)); ByteArrayContent content = new ByteArrayContent(contentBytes.ToArray()); DACPRequest request = new DACPRequest("/ctrl-int/1/controlpromptentry"); request.HttpContent = content; try { await SubmitRequestAsync(request).ConfigureAwait(false); } catch { return(false); } return(true); }
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); }
public async Task <bool> SendAppleTVKeyboardSecureTextAsync(string value) { if (!_appleTVKeyboardSecureText || string.IsNullOrEmpty(_appleTVKeyboardSecureTextCertificate) || string.IsNullOrEmpty(_appleTVKeyboardSecureTextChallenge)) { return(false); } byte[] encryptedBytes; try { // Read the certificate byte[] certificateBytes = BitUtility.FromHexString(_appleTVKeyboardSecureTextCertificate); var certificate = new X509Certificate(certificateBytes); // X509Certificate gives us the ASN.1 DER-encoded RSA public key var publicKeyEncoded = certificate.GetPublicKey(); if (publicKeyEncoded[0] != 0x30 || publicKeyEncoded[1] != 0x81) { throw new Exception(); } // Byte at index 2 is the length of the entire sequence, we can ignore this if (publicKeyEncoded[3] != 0x02 || publicKeyEncoded[4] != 0x81) { throw new Exception(); } // Length of the public key int length = publicKeyEncoded[5]; int position = 6; // Skip any padding at the beginning while (publicKeyEncoded[position] == 0x00) { length--; position++; } // Get the public key byte[] publicKey = new byte[length]; System.Buffer.BlockCopy(publicKeyEncoded, position, publicKey, 0, length); // Length/position of exponent position += length; if (publicKeyEncoded[position] != 0x02) { throw new Exception(); } position++; length = publicKeyEncoded[position]; position++; // Get the exponent byte[] exponent = new byte[length]; System.Buffer.BlockCopy(publicKeyEncoded, position, exponent, 0, length); // Get bytes to encrypt var bytesToEncode = new List <byte>(); bytesToEncode.AddRange(BitUtility.FromHexString(_appleTVKeyboardSecureTextChallenge)); bytesToEncode.AddRange(Encoding.UTF8.GetBytes(value)); // Set up RSA parameters var rsaParameters = new RSAParameters(); rsaParameters.Modulus = publicKey; rsaParameters.Exponent = exponent; // Create RSA provider using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportParameters(rsaParameters); // Encrypt the source data encryptedBytes = rsa.Encrypt(bytesToEncode.ToArray(), false); } } catch { return(false); } // Create DACP request List <byte> contentBytes = new List <byte>(); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmcc", _appleTVKeyboardSessionID)); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmbe", "PromptDone")); contentBytes.AddRange(DACPUtility.GetDACPFormattedBytes("cmae", BitUtility.ToHexString(encryptedBytes))); ByteArrayContent content = new ByteArrayContent(contentBytes.ToArray()); DACPRequest request = new DACPRequest("/ctrl-int/1/controlpromptentry"); request.HttpContent = content; try { await SubmitRequestAsync(request).ConfigureAwait(false); } 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)); }
protected async Task <bool> GetDatabasesAsync() { DACPRequest request = new DACPRequest("/databases"); try { var databases = await GetListAsync(request, n => DACPDatabase.GetDatabase(this, n)).ConfigureAwait(false); if (databases == null || databases.Count == 0) { return(false); } List <DACPDatabase> newSharedDatabases = new List <DACPDatabase>(); for (int i = 0; i < databases.Count; i++) { var db = databases[i]; // The main database will be first in the list if (i == 0) { if (MainDatabase != null && MainDatabase.ID == db.ID) { continue; } bool success = await db.RequestContainersAsync().ConfigureAwait(false); if (!success) { return(false); } MainDatabase = db; continue; } // Shared database if (db.Type == DatabaseType.Shared) { newSharedDatabases.Add(db); continue; } // Internet Radio if (db.Type == DatabaseType.InternetRadio) { if (InternetRadioDatabase != null && InternetRadioDatabase.ID == db.ID) { continue; } InternetRadioDatabase = db; continue; } // iTunes Radio if (db.Type == DatabaseType.iTunesRadio) { if (iTunesRadioDatabase != null && iTunesRadioDatabase.ID == db.ID) { continue; } iTunesRadioDatabase = (iTunesRadioDatabase)db; // Attempt to load the stations asynchronously to determine whether iTunes Radio is enabled. var task = iTunesRadioDatabase.RequestStationsAsync(); continue; } } // Update shared databases Dictionary <int, DACPDatabase> removedSharedDBs = SharedDatabases.ToDictionary(db => db.ID); foreach (var sharedDB in newSharedDatabases) { removedSharedDBs.Remove(sharedDB.ID); if (SharedDatabases.Any(db => db.ID == sharedDB.ID)) { continue; } SharedDatabases.Add(sharedDB); } foreach (DACPDatabase db in removedSharedDBs.Values) { SharedDatabases.Remove(db); } } catch (Exception e) { HandleHTTPException(request, e); return(false); } return(true); }
internal void HandleHTTPException(DACPRequest request, Exception e) { HandleHTTPException(request.GetURI(), e); }
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); }