# Easily add a video reel to your MAUI app (like Instagram)

# Introduction

Social media apps like Instagram use full-page scrollable video controls called "reels" *(named after the device used to store film - or garden hoses)* which allow the user to swipe or scroll up and down in order to switch between videos *(or other content)*.

In this blog post, I will demonstrate how you can easily add a feature like a video reel to your own .NET MAUI app in a few simple steps. The idea is that we can scroll up and down and the video that becomes visible will start playing while the previously playing video gets stopped. All we need for this is a *CollectionView* and the [MediaElement](https://learn.microsoft.com/dotnet/communitytoolkit/maui/views/mediaelement) from the *MAUI Community Toolkit (MCT)*.

After defining the model, we'll first have a look at how we can accomplish the desired layout, which is a full-page view that allows to swipe up and down and always stops at the next element. Afterwards, we'll implement the automatic play/stop functionality.

> This post is based on a [Stack Overflow answer](https://stackoverflow.com/a/78408360/4308455) that I recently wrote.

As always, you can have a look at the full code in the [sample repository](https://github.com/ewerspej/maui-samples) on GitHub.

Let's do it!

# Model

The model for the reel is quite simple, we just need to create a `VideoModel` with a few properties, e.g. `Title`, `VideoUri` and `IsPlaying`:

```csharp
public partial class VideoModel(string title, string videoUri) : ObservableObject
{
    public string Title { get; } = title;
    public string VideoUri { get; } = videoUri;

    [ObservableProperty]
    private bool _isPlaying;
}
```

The `IsPlaying` property is an [auto-generated](https://blog.ewers-peters.de/introduction-to-mvvm-source-generators-for-dotnet) observable property, which we will need later on to control the automatic play/stop functionality.

# ViewModel

In the ViewModel, we can define several instances of `VideoModel` using some sample video files that are stored locally in the repository, like so:

```csharp
public partial class ReelViewModel : ObservableObject
{
    private const string FrogVideo = "https://github.com/ewerspej/maui-samples/blob/main/assets/frog.mp4?raw=true";
    private const string BuckVideo = "https://github.com/ewerspej/maui-samples/blob/main/assets/bigbuckbunny.mp4?raw=true";

    [ObservableProperty]
    private ObservableCollection<VideoModel> _videos;

    public ReelViewModel()
    {
        Videos =
        [
            new VideoModel("First", FrogVideo),
            new VideoModel("Second", BuckVideo),
            new VideoModel("Third", FrogVideo),
            new VideoModel("Fourth", BuckVideo),
            new VideoModel("Fifth", FrogVideo),
            new VideoModel("Sixth", BuckVideo)
        ];
    }
}
```

Later on, we can then bind to the `Videos` collection in XAML.

# Basic Layout

In order to create the reel, we first need to add a *CollectionView* inside a *Grid*. The *CollectionView* will host the `VideoModel` instances. We want only a single item to be visible at a time, so we'll add a [LinearItemsLayout](https://learn.microsoft.com/dotnet/maui/user-interface/controls/collectionview/layout#vertical-list) and give the layout in the DataTemplate the ***same***`HeightRequest` as the *CollectionView*. In order to make the scrolling of the *CollectionView* stop at the next element, we'll also need to use [snap points](https://learn.microsoft.com/dotnet/maui/user-interface/controls/collectionview/scrolling#snap-points).

```xml
<Grid>

  <CollectionView
    HeightRequest="500"
    HorizontalOptions="Fill"
    VerticalOptions="Center"
    ItemsSource="{Binding Videos}"
    Scrolled="ItemsView_OnScrolled">

    <CollectionView.ItemsLayout>
      <LinearItemsLayout
        Orientation="Vertical"
        SnapPointsType="MandatorySingle"
        SnapPointsAlignment="Center" />
    </CollectionView.ItemsLayout>

    <CollectionView.ItemTemplate>
      <DataTemplate x:DataType="models:VideoModel">
        <Grid
          HeightRequest="500"
          HorizontalOptions="Fill"
          BackgroundColor="LightGreen">

          <Label
            Text="{Binding Title}"
            VerticalOptions="Center"
            HorizontalOptions="Center"
            FontSize="Title" />

        </Grid>
      </DataTemplate>
    </CollectionView.ItemTemplate>

  </CollectionView>

</Grid>
```

The result should something like this and we can scroll back and forth between the elements, with always only a single item visible at a time:

%[https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNWkxNnMzMmM2dmZrc2Q5bmpjemE2MXliZ3A4enVxeHZ0Z21uNjhpNyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/YOon2PAVhJlyFbSQYM/giphy.gif] 

That's already pretty neat. Next, let's add the video player and the automatic play/stop functionality.

# Automatic Play/Stop

Before we can implement the play/stop functionality, we need to install the [CommunityToolkit.Maui.MediaElement](https://www.nuget.org/packages/CommunityToolkit.Maui.MediaElement) package from *nuget.org* and initialize the *MediaElement* in our *MauiProgram* class before we can use it:

```csharp
var builder = MauiApp.CreateBuilder();
builder
    .UseMauiApp<App>()
    .UseMauiCommunityToolkitMediaElement() // add this line
```

In order to automatically play a video that is hosted in a *MediaElement*, we need to work with a little trick. Since the *MediaElement* doesn't expose any bindable and settable `IsPlaying` property, we need to add that ourselves by extending the class with a [BindableProperty](https://learn.microsoft.com/dotnet/maui/fundamentals/bindable-properties):

```csharp
public class ExtendedMediaElement : MediaElement
{
    public bool IsPlaying
    {
        get => (bool)GetValue(IsPlayingProperty);
        set => SetValue(IsPlayingProperty, value);
    }

    public static readonly BindableProperty IsPlayingProperty = BindableProperty.Create(nameof(IsPlaying), typeof(bool), typeof(ExtendedMediaElement), false, propertyChanged: OnIsPlayingPropertyChanged);

    private static void OnIsPlayingPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var mediaElement = (ExtendedMediaElement)bindable;

        if (newValue is true)
        {
            mediaElement.Play();
        }
        else
        {
            mediaElement.Stop();
        }
    }
}
```

By using the `OnIsPlayingPropertyChanged` event handler, we can decide whether to play or stop the video whenever the `IsPlaying` property gets updated (via a binding).

With this in place, we can now add our *ExtendedMediaElement* to the XAML:

```xml
<DataTemplate x:DataType="models:VideoModel">
  <Grid
    HeightRequest="{Binding Height, Source={x:Reference Reels}}"
    HorizontalOptions="Fill">

    <media:ExtendedMediaElement
      Aspect="Fill"
      Source="{Binding VideoUri}"
      ShouldAutoPlay="False"
      ShouldLoopPlayback="True"
      ShouldShowPlaybackControls="False"
      IsPlaying="{Binding IsPlaying}" />

    <Label
      Text="{Binding Title}"
      VerticalOptions="Center"
      HorizontalOptions="Center"
      FontSize="Title" />

  </Grid>
</DataTemplate>
```

Now, a video is only played and stopped whenever the `IsPlaying` property of the associated `VideoModel` instance changes.

However, we're not entirely done yet, one important part is still missing. We want the videos to automatically start and stop whenever an element is scrolled into and out of view. For this, we need to hook up an event handler for the `Scrolled` event of the *CollectionView*, where we then update the `IsPlaying` property of each item in the underlying collection:

```xml
<CollectionView
  HeightRequest="{Binding Height, Source={x:Reference Reels}}"
  HorizontalOptions="Fill"
  VerticalOptions="Center"
  ItemsSource="{Binding Videos}"
  Scrolled="ItemsView_OnScrolled">

  <!-- ... -->

</CollectionView>
```

The event handler lives in the code-behind of our page and iterates through the `Videos` collection to update the `IsPlaying` property of the `VideoModel` instances:

```csharp
public partial class ReelPage : ContentPage
{
    private readonly ReelViewModel _vm;

    public ReelPage(ReelViewModel vm)
    {
        InitializeComponent();
        BindingContext = _vm = vm;
    }

    private void ItemsView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
    {
        var itemIndex = e.CenterItemIndex;

        _vm.Videos[itemIndex].IsPlaying = true;

        foreach (var myModel in _vm.Videos)
        {
            if (myModel != _vm.Videos[itemIndex])
            {
                myModel.IsPlaying = false;
            }
        }
    }
}
```

Almost there. Now, the automatic play/stop should already be working. Let's touch up the XAML a little bit and have a look at the final result next.

# Final Full-Page Layout

Last, but not least, in order to have the *CollectionView* take up the entire available space of the page, we can simply bind the `HeightRequest` of the *CollectionView* as well as the *Grid* in the *DataTemplate* to the rendered `Height` of the page to end up with the following final layout:

```xml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
  x:Class="MauiSamples.Views.ReelPage"
  xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:models="clr-namespace:MauiSamples.Models"
  xmlns:viewModels="clr-namespace:MauiSamples.ViewModels"
  xmlns:media="clr-namespace:MauiSamples.CustomControls.Media"
  x:Name="Reels"
  x:DataType="viewModels:ReelViewModel"
  Shell.NavBarIsVisible="False">

  <Grid>

    <CollectionView
      HeightRequest="{Binding Height, Source={x:Reference Reels}}"
      HorizontalOptions="Fill"
      VerticalOptions="Center"
      ItemsSource="{Binding Videos}"
      Scrolled="ItemsView_OnScrolled">

      <CollectionView.ItemsLayout>
        <LinearItemsLayout
          Orientation="Vertical"
          SnapPointsType="MandatorySingle"
          SnapPointsAlignment="Center" />
      </CollectionView.ItemsLayout>

      <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="models:VideoModel">
          <Grid
            HeightRequest="{Binding Height, Source={x:Reference Reels}}"
            HorizontalOptions="Fill">

            <media:ExtendedMediaElement
              Aspect="AspectFill"
              Source="{Binding VideoUri}"
              ShouldAutoPlay="False"
              ShouldLoopPlayback="True"
              ShouldShowPlaybackControls="False"
              IsPlaying="{Binding IsPlaying}" />

            <Label
              Text="{Binding Title}"
              VerticalOptions="Center"
              HorizontalOptions="Center"
              FontSize="Title" />

          </Grid>
        </DataTemplate>
      </CollectionView.ItemTemplate>

    </CollectionView>

  </Grid>

</ContentPage>
```

Now, when we run the app, we have a full-page reel control:

%[https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExN3JsOXY0cTcxaTQ3bXQ2cWtjNDgzaXg0eWJ0anVzMWw3bmhvMXo3ZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EEJz7teOtuJBC22nou/giphy.gif] 

🏆 Fantastic, I'm excited about the result.

# Conclusions and next steps

As I demonstrated here, adding a scrollable video control similar to Instagram's reels to your .NET MAUI app is quite simple. A neat addition to this would be to make the entire page fullscreen, but that would be too much for a single blog post. The main focus here was supposed to be on how to combine the *CollectionView* and the *MediaElement* to create a reel and that's surprisingly easy to achieve.

> **Note:** I didn't touch on any issues regarding caching or performance in this post. When the list of videos gets very long or when items are incrementally loaded, e.g. when using the infinite scroll capabilities of CollectionView, we might observe performance degradation, which should then be addressed separately.  
> Thanks to [Gerald Versluis](https://jfversluis.dev/) for pointing this out.

If you enjoyed this blog [post](https://hashnode.com/@ewerspej), then follow me on [**LinkedIn**](https://www.linkedin.com/in/jewerspeters), subscribe to this [**blog**](https://hashnode.com/@ewerspej) and star the [**GitHub repository**](https://github.com/ewerspej/maui-samples) for this post so you don't miss out on any future posts and developments. And remember that sharing is caring.

## **Attributions**

Title photo by [Markus Spiske](https://unsplash.com/@markusspiske?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-pair-of-sunglasses-on-a-table-1C202gyp2P0?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)
