Mórbido offers podcasts, streaming videos, news, and other content for their fans through a Xamarin app that is available for Android users.

Key technologies used

  • Xamarin.Forms
  • Visual Studio 2015
  • Azure Media Services
  • Azure SQL Database
  • Azure Blob storage
  • Azure Virtual Machines

Core project team

  • Ricardo Pons (@RicardoPonsDev) – Senior Developer, Mórbido
  • Vianey Juarez Araujo (@VIANEYsitaa) – Technical Evangelist, Microsoft

Customer profile

Mórbido is a multiplatform content generator. The company started as a film festival, but now their services extend to a website, TV network, film distributor, radio show, social media pages, and a printed magazine.

Mórbido’s content revolves around horror, science fiction, and fantasy and generates information on a daily basis on all its platforms for more than 5 million people from all over Latin America.

Problem statement

“The constant and dynamic transformation of the entertainment industry, coupled with the insatiable interest of our fans to consume all kinds of content at all times, everywhere and in all possible devices and our project being a multiplatform generator of content and events throughout Latin America, led us to the conclusion that a mobile app was the only real option we had to concentrate everything; then we started developing an application that had the capacity and strength to satisfy our current needs and allow us to continue growing.” —Pablo Guisa Koestinger, Mórbido CEO

Mórbido needed a mobile app because they have different audience channels, such as the magazine, TV, movies, web page, and social media, and they need this app to gather all of these channels. With it, they will start collecting all the information from their users (or as they call them, fans) because currently they don’t have a way to know how many fans are cross-consuming their products. For example, they don’t have a way to know how many users who buy the magazine also are viewing the TV channel.

Through this app, Mórbido will have a way to know more about their fans and collect information about them. With this information, they will be able to offer specific promotions, discounts, or advertisements.

Solution, steps and delivery

Why Xamarin

Mórbido selected Xamarin because they are going to going to deliver this app for Universal Windows Platorm (UWP), Android, and iOS, so they wanted to take advantage of Xamarin multiplatform capabilities and reach most of the possible devices used by their fans.

The Mórbido development team is familiar with UWP development, so they already know XAML and C#. The learning curve for Xamarin was smaller than learning each native language for Android and iOS.

Challenges

One of the challenges encountered while developing the app was related to video and audio streaming. Mórbido uses the Smooth Streaming protocol to deliver content. The file name extension of the video that we get is “.ismv”, and the Xamarin native player is not compatible with it.

Mórbido decided to use Smooth Streaming in the back end because it offers the possibility to change video resolution in real time depending on the Internet connection speed of the client.

The video-streaming challenge was overcome by playing video through Rox Xamarin Video. This component allows the app to progressively play video from Azure Media Services. Also, this player has play and pause controls.

To use this component, we need to install it from NuGet using the following command:

Install-Package Rox.Xamarin.Video

Implementing the player in the project is really simple. After we get the video URL from the back-end web service, we just have to create a view to build the player. Then we assign the URL by binding.

<ContentPage
    x:Class="MorbidoXamarinClient.Views.PlayerView"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:roxv="clr-namespace_Rox;assembly=Rox.Xamarin.Video.Portable"
    xmlns:core="clr-namespace:Octane.Xam.VideoPlayer;assembly=Octane.Xam.VideoPlayer">
    <ContentPage.Content>
        <roxv:VideoView AutoPlay="True" x:Name="player" ShowController="True" Source="{Binding VideoURL, Mode=TwoWay}"/>
    </ContentPage.Content>
</ContentPage>

To play audio, we had to implement the Xamarin MediaManager component. To be able to play a podcast within the app, first we need to get the podcast URL.

var result = await podcastService.GetSmoothStreamingUriAsync(SelectedPodcast.Id,
cancelToke, loginService.CurrentToken.AccessToken);

After we get the URL, we need to add a specific format for Android (M3U8).

(format=m3u8-aapl-v3)

Now the player now can play the podcast.

The next step is to implement the device native player and assign the audio file it will play.

var implementation = new Plugin.MediaManager.MediaManagerImplementation();

MediaFile file = new MediaFile(uri, 
Plugin.MediaManager.Abstractions.Enums.MediaFileType.AudioUrl);

await implementation.AudioPlayer.Play(file);

It is important to mention that all of the timing and playback indicators of the file being played must be carried manually in the ViewModel podcast.

Another challenge that we found was presenting the news, because we needed to show the image within the news text title, as shown in the following image.

Screen shot of Noticias (news) page

Currently, the Xamarin built-in table-row appearance is an image next to text or just plain text, so we needed to create our own. To do so, we needed to add a custom layout and a custom view.

Custom layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:background="#FFDAFF7F"
   android:padding="8dp">
   <LinearLayout android:id="@+id/Text"
      android:orientation="vertical"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:paddingLeft="10dip">
      <TextView
         android:id="@+id/Text1"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textColor="#FF7F3300"
         android:textSize="20dip"
         android:textStyle="italic"
         />
      <TextView
         android:id="@+id/Text2"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textSize="14dip"
         android:textColor="#FF267F00"
         android:paddingLeft="100dip"
         />
   </LinearLayout>
   <ImageView
      android:id="@+id/Image"
      android:layout_width="48dp"
      android:layout_height="48dp"
      android:padding="5dp"
      android:src="@drawable/icon"
      android:layout_alignParentRight="true"
      />
</RelativeLayout >

Custom view

public class HomeScreenAdapter : BaseAdapter<TableItem> {
   List<TableItem> items;
   Activity context;
   public HomeScreenAdapter(Activity context, List<TableItem> items)
       : base()
   {
       this.context = context;
       this.items = items;
   }
   public override long GetItemId(int position)
   {
       return position;
   }
   public override TableItem this[int position]
   {
       get { return items[position]; }
   }
   public override int Count
   {
       get { return items.Count; }
   }
   public override View GetView(int position, View convertView, ViewGroup parent)
   {
       var item = items[position];
       View view = convertView;
       if (view == null) // no view to re-use, create new
           view = context.LayoutInflater.Inflate(Resource.Layout.CustomView, null);
       view.FindViewById<TextView>(Resource.Id.Text1).Text = item.Heading;
       view.FindViewById<TextView>(Resource.Id.Text2).Text = item.SubHeading;
       view.FindViewById<ImageView>(Resource.Id.Image).SetImageResource(item.ImageResourceId);
       return view;
   }
}

Code excerpts

The Mórbido app connects to the back end through HTTP requests. To make it secure, Mórbido implemented OAuth to be able to get the required info in JSON format, so then it could be deserialized and passed to the app in a clear way.

The following code shows how this back-end call is made. The user token is sent within the service call; in this way, we ensure that it is a secure request.

public async Task<IEnumerable<HomeDashboard> GetAllHomeDashboardsAsync (CancellationTokenSource cancelToken, string token="")
{
    using (var client = new HttpClient())
    {
        if(!string.IsNullOrEmpty(token))
            client.SetDefaultHeaders(token);
        var petitionUrl = new StringBuilder(Providers.ProviderSettings.UrlService);
        petitionUrl.Append("tables/HomeDashboard");
        var response = await client.GetAsync(petitionUrl.ToString(), cancelToken.Token);
        var result = await response.Context.ReadAsStringAsync();
        return await Task.Factory.StartNew(() =>
        {
            return JsonConvert.DeserializeObject<IEnumerable<MorbidoXamarinClient.HomeDashboard>>(result);
        });
    }
}

The Mórbido app has a principal dashboard, which creates an HttpClient class that calls the cloud service to get the info to display. A cancellation token is assigned in case the call is cancelled, so the app doesn’t have to wait until the service responds. When the method recieves the JSON response, we parse it to a class that can be used by the application.

public async Task<Ienumerable<HomeDashboard>> GetAllHomeDashboardsAsync(CancellationTokenSource cancelToken, string token="")
{
    using(var client = new HttpCllient())
    {
        if (!string.IsNullOrEmpty(token))
            client.SetDefaultHeaders(token);
        var petitionUrl = new StringBuilder(Provider.ProviderSettings.UrlService);
        petitionUrl.Append("tables/HomeDashboard");
        var response = await client.GetAsync(petitionUrl.ToSting(), cancelToken.Token);
        return await Task.Factory.StartNew(() =>
        {
            return JsonConvert.DeserializeObject<IEnumerable<MorbidoXamarinClient.Models.HomeDashboard>>(result);
        });
    }
}

The resulting window looks like this:

Screen shot of Principal page (dashboard)

To get the podcast feed, it’s a similar method, but here we need to get the podcasts by category.

public async Task<Ienumerable<HomeDashboard>> GetAllHomeDashboardsAsync(CancellationTokenSource cancelToken, string token="")
{
    using(var client = new HttpCllient())
    {
        if (!string.IsNullOrEmpty(token))
            client.SetDefaultHeaders(token);
        var petitionUrl = new StringBuilder(Provider.ProviderSettings.UrlService);
        petitionUrl.Append("api/Podcast/GetPodcastByCategoryId");
        var response = await client.GetAsync(petitionUrl.ToSting(), cancelToken.Token);
        return await Task.Factory.StartNew(() =>
        {
            return JsonConvert.DeserializeObject<IEnumerable<MorbidoXamarinClient.Models.Podcast>>(result);
        });
    }
}

The resulting window looks like this:

Screen shot of Podcast page

To reproduce a selected podcast, we need to get the podcast URL from the web service. To reproduce the content, we need to replace the MPD format with M3U8; then we put the player in stop mode (in case it’s currently playing). At this point, we discovered that AudioPlayer doesn’t allow you to play the same file more than once. So we added an alternate URL to play in the module, enabling us to play the audio from the service.

private async void SelectedItemCommandExecute(object obj)
{
    IsBusy = true;
    try
    {
    if(obj!=null && obj is Infrastructure.Models.Podcast && !string.IsNullOrEmpty((obj as Infrastructure.Models.Podcast).Id))
        {
            SelectedPodcast.Duration = 100;
            IsAutoPlay = false;
            var result = await podcastService.GetSmoothStreamingUriAsync(SelectedPodcast.Id, cancelToken, loginService.currentToken.AccessToken);
            var url = Infrastructure.Helpers.GetLinksHelper.GetLinks(result);
            SelectedPodcast.PodcastUrl = url.FirstOrDefault();

            string uri = string.Empty();
            if(SelectedPodcast.PodcastUrl.Constrains("(format=mp-time-csf)")
            {
                uri = SelectedPodcast.PodcastUrl.Replace("(format=mpd-time-csf)", "(format=m3u8-aapl-v3)");
            }
            else if (!SelectedPodcast.PodcastUrl.Contains("(format=m3u8-aapl-v3)"))
            {
                uri = SelectedPodcast.PodcastUrl + "(format=m3u8-aapl-v3)";
            }

            await implementation.AudioPlayer.Stop();
            //for dispose player and change the current track
            MediaFile disposerFile = new Mediafile("www.urlinthemiddle.com", Plugin.MediaManager.Abstractions.Enums.MediaFileType.AudioUrl);
            await implementation.AudioPlayer.Play(disposerFile);

            await Task.Delay(1000);
            MediaFile file = new Mediafile(uri, Plugin.MEdiaManager.Abstraction.Enums.MediaFileType.AudioUrl);
            await implemetantion.AudioPlayer.Play(file);

            IsBusy = false;
            IsAutoPlay = true;
        }
    }
    catch (Exception exception){
        IsBusy = false;
        //Send an alert showing there was an error acquiring the url
        await userDialogService.AlertAsync("No se ha podido obtener la información, por favor verifica tu conexión a internet.", "Advertencia", "Aceptar");
    }
    IsBusy= false;
}

Screen shot of podcast player

The following diagram shows the architecture of the app.

Architecture diagram

General lessons

It was really difficult to find information regarding streaming integration with Xamarin. We expect this article to help other developers solve this challenge more quickly.

We tried many NuGet packages to implement streaming, but they crashed and closed the application during testing.

Opportunities going forward

This application was launched at the end of March 2017 for Android and is expected to launch for iOS and UWP later in 2017. Mórbido will use Xamarin for these apps, so they can reuse most of the functionality and speed up the development phase for the other devices.

Another opportunity is related to storing metrics from users. In a future stage of the application, they plan to collect metrics from the users on the app, such as how much time a user spends in each section or the most visited section, and then target ads or show content to users depending on the most visited sections (horror, science fiction, and so on).

Conclusion

The impact of this app is related to Mórbido fans and Mórbido itself. Mórbido will begin collecting information about their fans in a database, such as how many people they are reaching and the various countries they are from, and start offering more content. The fans will be affected because they can watch Mórbido movies without a TV channel subscription and listen to the podcast and news related to horror and science-fiction movies.

Additional resources