private async Task <ConnectionResult> LoginAsync()
        {
            DacpRequest request = new DacpRequest("/login");

            request.QueryParameters["pairing-guid"] = "0x" + PairingCode;
            request.IncludeSessionID = false;

            try
            {
                var response = await SendRequestAsync(request).ConfigureAwait(false);

                // Process response
                var nodes = DacpNodeDictionary.Parse(response.Nodes);

                if (!nodes.ContainsKey("mlid"))
                {
                    return(ConnectionResult.InvalidPIN);
                }

                SessionID = nodes.GetInt("mlid");
                UpdateNowPlayingAlbumArtUri();
            }
            catch (DacpRequestException e)
            {
                int statusCode = (int)e.Response.StatusCode;
                if (statusCode >= 500 && statusCode <= 599)
                {
                    return(ConnectionResult.InvalidPIN);
                }
                return(ConnectionResult.ConnectionError);
            }
            catch { return(ConnectionResult.ConnectionError); }

            return(ConnectionResult.Success);
        }
Example #2
0
        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);
        }
        private async Task <bool> GetVolumeLevelAsync()
        {
            DacpRequest request = new DacpRequest("/ctrl-int/1/getproperty");

            request.QueryParameters["properties"] = "dmcp.volume";

            try
            {
                var response = await SendRequestAsync(request).ConfigureAwait(false);

                var nodes = DacpNodeDictionary.Parse(response.Nodes);

                CurrentVolumeLevel = nodes.GetInt("cmvo");
            }
            catch { return(false); }
            return(true);
        }
        private async Task <bool> GetTrackTimePositionAsync()
        {
            DacpRequest request = new DacpRequest("/ctrl-int/1/getproperty");

            request.QueryParameters["properties"] = "dacp.playingtime";

            try
            {
                var response = await SendRequestAsync(request).ConfigureAwait(false);

                var nodes = DacpNodeDictionary.Parse(response.Nodes);

                int totalMS     = nodes.GetInt("cast");
                int?remainingMS = nodes.GetNullableInt("cant");
                UpdateTrackTime(totalMS, remainingMS);
            }
            catch { return(false); }
            return(true);
        }
        private 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 SendRequestAsync(request).ConfigureAwait(false);

                if (cancellationToken.IsCancellationRequested)
                {
                    return(false);
                }

                var nodes = DacpNodeDictionary.Parse(response.Nodes);
                CurrentLibraryUpdateNumber = nodes.GetInt("musr");
            }
            catch { return(false); }
            return(true);
        }
        private async Task <bool> GetServerCapabilitiesAsync()
        {
            DacpRequest request = new DacpRequest("/ctrl-int");

            request.IncludeSessionID = false;

            try
            {
                var response = await SendRequestAsync(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)
                    {
                        ServerSupportsPlayQueue = 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)
                    {
                        ServerSupportsGeniusShuffle = true;
                    }
                }
            }
            catch { return(false); }
            return(true);
        }
        private async Task <bool> GetServerInfoAsync()
        {
            DacpRequest request = new DacpRequest("/server-info");

            request.IncludeSessionID = false;

            try
            {
                var response = await SendRequestAsync(request).ConfigureAwait(false);

                // Process response
                ServerVersion = response.HTTPResponse.Headers.GetValueOrDefault("DAAP-Server");

                var nodes = DacpNodeDictionary.Parse(response.Nodes);
                ServerName = nodes.GetString("minm");
                //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"));
                    }
                    ServerMacAddresses = macAddresses.ToArray();
                }
            }
            catch { return(false); }
            return(true);
        }
        private async Task <bool> GetSpeakersAsync()
        {
            DacpRequest request = new DacpRequest("/ctrl-int/1/getspeakers");

            try
            {
                var response = await SendRequestAsync(request).ConfigureAwait(false);

                var speakerNodes = response.Nodes.Where(n => n.Key == "mdcl").Select(n => DacpNodeDictionary.Parse(n.Value)).ToList();

                var speakers = Speakers;

                // Determine whether we need to replace the list of speakers
                bool replaceSpeakers = false;
                if (speakers == null || speakers.Count != speakerNodes.Count)
                {
                    replaceSpeakers = true;
                }
                else
                {
                    // Determine whether we still have the same speaker IDs
                    for (int i = 0; i < speakers.Count; i++)
                    {
                        if (speakers[i].ID != (UInt64)speakerNodes[i].GetLong("msma"))
                        {
                            replaceSpeakers = true;
                            break;
                        }
                    }
                }

                // Create the new list of speakers or update the existing speakers
                if (replaceSpeakers)
                {
                    Speakers = speakerNodes.Select(n => new AirPlaySpeaker(this, n)).ToList();
                }
                else
                {
                    for (int i = 0; i < speakers.Count; i++)
                    {
                        speakers[i].ProcessNodes(speakerNodes[i]);
                    }
                }
            }
            catch { return(false); }
            return(true);
        }
        private async Task <bool> GetPlayStatusUpdateAsync(CancellationToken cancellationToken)
        {
            // Do not pass the cancellation token to the HTTP request since canceling 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 SendRequestAsync(request).ConfigureAwait(false);

                // Do we still need to process this response?
                if (cancellationToken.IsCancellationRequested)
                {
                    return(false);
                }

                // Process response
                var nodes = DacpNodeDictionary.Parse(response.Nodes);
                _playStatusRevisionNumber = nodes.GetInt("cmsr");

                // Current item and container IDs
                if (nodes.ContainsKey("canp"))
                {
                    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;
                }
                CurrentItemSignature = string.Format("{0}|{1}|{2}|{3}", CurrentDatabaseID, CurrentContainerID, CurrentContainerItemID, CurrentItemID);

                // Current item info
                CurrentArtistName        = nodes.GetString("cana");
                CurrentAlbumName         = nodes.GetString("canl");
                CurrentSongName          = nodes.GetString("cann");
                CurrentAlbumPersistentID = (UInt64)nodes.GetLong("asai");

                // Play state
                CurrentPlayState = (PlayState)nodes.GetByte("caps");

                // Track time
                UpdateTrackTime(nodes.GetInt("cast"), nodes.GetNullableInt("cant"));

                // Shuffle
                int caas = nodes.GetInt("caas");
                IsShuffleAvailable = (caas & (1 << 1)) != 0;
                CurrentShuffleMode = nodes.GetBool("cash");

                // Repeat
                int caar = nodes.GetInt("caar");
                IsRepeatOneAvailable = (caar & (1 << 1)) != 0;
                IsRepeatAllAvailable = (caar & (1 << 2)) != 0;
                IsRepeatAvailable    = (IsRepeatOneAvailable || IsRepeatAllAvailable);
                CurrentRepeatMode    = (RepeatMode)nodes.GetByte("carp");

                //CurrentMediaKind = nodes.GetInt("cmmk");
                //ShowUserRating = nodes.GetBool("casu");

                // dacp.visualizer
                IsVisualizerEnabled = nodes.GetBool("cavs");
                // dacp.visualizerenabled
                IsVisualizerAvailable = nodes.GetBool("cave");
                // dacp.fullscreen
                IsFullScreenModeEnabled = nodes.GetBool("cafs");
                // dacp.fullscreenenabled
                IsFullScreenModeAvailable = nodes.GetBool("cafe");

                // iTunes Radio
                iTunesRadioControlState newiTunesRadioControlState = 0;
                if (iTunesRadioDatabase != null && iTunesRadioDatabase.ID == CurrentDatabaseID)
                {
                    IsPlayingiTunesRadio          = true;
                    CurrentiTunesRadioStationName = nodes.GetString("ceNR");

                    // caks = 1 when the next button is disabled, and 2 when it's enabled
                    if (nodes.GetByte("caks") != 1)
                    {
                        newiTunesRadioControlState |= iTunesRadioControlState.NextButtonEnabled;
                    }

                    // "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.
                    if (nodes.ContainsKey("aelb"))
                    {
                        newiTunesRadioControlState |= iTunesRadioControlState.MenuEnabled;
                    }

                    if (nodes.GetByte("aels") == 2)
                    {
                        newiTunesRadioControlState |= iTunesRadioControlState.CurrentSongFavorited;
                    }
                }
                else
                {
                    IsPlayingiTunesRadio = false;
                }

                CurrentiTunesRadioControlState = newiTunesRadioControlState;

                IsTrackTimePositionBarEnabled = nodes.GetBool("casc", true);

                // Genius Shuffle
                IsPlayingGeniusShuffle = nodes.GetBool("ceGs");
                // There are two other nodes related to Genius Shuffle, "ceGS" and "aeGs" (currently unknown)

                if (CurrentPlayState == PlayState.FastForward || CurrentPlayState == PlayState.Rewind)
                {
                    BeginRepeatedTrackTimeRequests();
                }

                await GetVolumeLevelAsync().ConfigureAwait(false);
                await GetSpeakersAsync().ConfigureAwait(false);

                //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();

                PlayStatusUpdated.RaiseOnUIThread(this, new EventArgs());
            }
            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));
 }
Example #11
0
 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));
 }
Example #12
0
 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));
 }
Example #13
0
 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));
 }