For my radio app Cuterdio I implemented CarPlay. There was no example for this in Xamarin, so I decided to create one (GitHub).

First of all you need a special entitlement for CarPlay. See here for a navigation app and audio app.
Add the entitlement to your project and don’t forget to link the entitlements in your project.
In this example we create a simple music player which plays audio streams via the amazing XamarinMediaManager.

In our iOS project we init the delegates in the AppDelegate.cs:

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
  Forms.Forms.Init();
  LoadApplication(new App());

  CrossMediaManager.Current.Init();
  CarIntegrationBridge.Init();

  return base.FinishedLaunching(app, options);
}

The delegates are registered as follow:

public class CarIntegrationBridge : ICarIntegrationBridge
{
  public static void Init()
  {
    PlayableContentDelegate playableContentDelegate = new PlayableContentDelegate();
    MPPlayableContentManager.Shared.Delegate = playableContentDelegate;

    PlayableContentDataSource playableContentDataSource = new PlayableContentDataSource();
    MPPlayableContentManager.Shared.DataSource = playableContentDataSource;
  }
}

Now we define our datasource. In my example I added radio stations with name and url. We have to define the menu items count and how a menu item is displayed (name, icon, …):

internal class PlayableContentDataSource : MPPlayableContentDataSource
{
  public static List<Station> Stations = new List<Station>
  {
    new Station{Name = "Rainbow radio", Url = "https://stream.rockantenne.de/rockantenne/stream/mp3"},
    new Station{Name = "Unicorn radio", Url = "http://play.rockantenne.de/heavy-metal.m3u"}
  };
  
  public override MPContentItem ContentItem(NSIndexPath indexPath)
  {
    var station = Stations[indexPath.Section];
    var item = new MPContentItem(station.Url);
    item.Title = station.Name;
    item.Playable = true;
    item.StreamingContent = true;
    var artWork = GetImageFromUrl("station.png");
    if (artWork != null)
    {
      item.Artwork = artWork;
    }
    return item;
  }

  public override nint NumberOfChildItems(NSIndexPath indexPath)
  {
    if (indexPath.GetIndexes().Length == 0)
    {
      return Stations.Count;
    }
    throw new NotImplementedException();
  }

  private MPMediaItemArtwork GetImageFromUrl(string imagePath)
  {
    MPMediaItemArtwork result = null;
    try
    {
      using (var nsUrl = new NSUrl(imagePath))
      {
        using (var data = NSData.FromUrl(nsUrl))
        {
          var image = UIImage.LoadFromData(data);
          result = new MPMediaItemArtwork(image);
        }
      }
    }
    catch
    {
      UIImage image = UIImage.FromBundle(imagePath);
      if (image != null)
      {
        result = new MPMediaItemArtwork(image);
      }
    }
    return result;
  }
}

Now we have to decide what is todo, if an item is taped.
The simulator have an other behavior than a real device. So I hacked a solution for calling the NowPlayingScene.

internal class PlayableContentDelegate : MPPlayableContentDelegate
{
  public override void InitiatePlaybackOfContentItem(
    MPPlayableContentManager contentManager, NSIndexPath indexPath, Action<NSError> completionHandler)
  {
    Execute(contentManager, indexPath);
    completionHandler?.Invoke(null);
  }

  private void Execute(MPPlayableContentManager contentManager, NSIndexPath indexPath)
  {
    DispatchQueue.MainQueue.DispatchAsync(async () => await ItemSelectedAsync(contentManager, indexPath));
  }

  private async Task ItemSelectedAsync(MPPlayableContentManager contentManager, NSIndexPath indexPath)
  {
    // Play
    var station = PlayableContentDataSource.Stations[indexPath.Section];
    await CrossMediaManager.Current.Play(station.Url);

    // Set playing identifier
    MPContentItem item = contentManager.DataSource.ContentItem(indexPath);
    contentManager.NowPlayingIdentifiers = new[] { item.Identifier };

    // Update on simulator
    if (DeviceInfo.DeviceType == DeviceType.Virtual)
    {
      InvokeOnMainThread(() =>
      {
        UIApplication.SharedApplication.EndReceivingRemoteControlEvents();
        UIApplication.SharedApplication.BeginReceivingRemoteControlEvents();
      });
    }
  }
}

To reload the data (e.g. if you change the stations), you have to call this:

public void ReloadStations()
{
  MPPlayableContentManager.Shared?.ReloadData();
}