Loupe

Afficher une vidéo DailyMotion dans une application Xamarin.Android

Dailymotion propose, sur son site Web, une grande liste de SDKs officiels, permettant de couvrir la grande majorité des plateformes existantes à l’heure actuelle:

image

Cependant, l’éditeur ne mets pas à disposition, des développeurs, de SDK utilisable dans le cadre d’une application Xamarin.Android. Fort heureusement, comme souvent avec Xamarin, il est possible de s’appuyer sur le code source du SDK en version Android, accessible depuis Github: https://github.com/dailymotion/dailymotion-sdk-android

Ainsi, il suffit de prendre le code source Java pour Android et de “l’adapter”, en C#, pour une application Xamarin. On a donc besoin d’une classe qui va représenter notre vue et qui va hériter de la classe “WebView” car le player DailyMotion s’appuie sur un objet HTML5 pour afficher les vidéos:

public class DailyMotionWebVideoView : WebView
{
    private WebSettings mWebSettings;
    private WebChromeClient mChromeClient;

    private String mEmbedUrl = "http://www.dailymotion.com/embed/video/{0}?html=1&fullscreen={1}&app={2}&api=location";
    private String mExtraUA = "; DailymotionEmbedSDK 1.0";
    private FrameLayout mVideoLayout;
    private FrameLayout mRootLayout;
    private Boolean mAllowAutomaticNativeFullscreen = false;

    public bool AutoPlay { get; set; }
    public bool IsFullScreen { get; set; }
    public WebChromeClient.ICustomViewCallback ViewCallback { get; set; }
    public VideoView CustomVideoView { get; set; }

    #region Constructors

    public DailyMotionWebVideoView(IntPtr javaReference, JniHandleOwnership transfer)
        : base(javaReference, transfer)
    {
        this.Initialize();
    }

    public DailyMotionWebVideoView(Context context)
        : base(context)
    {
        this.Initialize();
    }

    public DailyMotionWebVideoView(Context context, IAttributeSet attrs)
        : base(context, attrs)
    {
        this.Initialize();
    }

    public DailyMotionWebVideoView(Context context, IAttributeSet attrs, int defStyleAttr)
        : base(context, attrs, defStyleAttr)
    {
        this.Initialize();
    }

    public DailyMotionWebVideoView(Context context, IAttributeSet attrs, int defStyleAttr, bool privateBrowsing)
        : base(context, attrs, defStyleAttr, privateBrowsing)
    {
        this.Initialize();
    }

    public DailyMotionWebVideoView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes)
        : base(context, attrs, defStyleAttr, defStyleRes)
    {
        this.Initialize();
    }

    #endregion

    private void Initialize()
    {
        //The topmost layout of the window where the actual VideoView will be added to
        mRootLayout = (FrameLayout)((Activity)this.Context).Window.DecorView;

        mWebSettings = this.Settings;
        mWebSettings.JavaScriptEnabled = true;
        mWebSettings.SetPluginState(WebSettings.PluginState.On);
        mWebSettings.UserAgentString = mWebSettings.UserAgentString + mExtraUA;

        if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr1)
        {
            mWebSettings.MediaPlaybackRequiresUserGesture = true;
        }

        mChromeClient = new DailyMotionWebChromeClient(this, this.Context);

        SetWebChromeClient(mChromeClient);
        SetWebViewClient(new DailyMotionWebViewClient(this));
    }

    public void CallPlayerMethod(string method)
    {
        LoadUrl("javascript:player.api(\"" + method + "\")");
    }

    public void SetVideoId(string videoId)
    {
        LoadUrl(string.Format(mEmbedUrl, videoId, mAllowAutomaticNativeFullscreen, this.Context.PackageName));
    }

    public void SetVideoId(string videoId, bool autoPlay)
    {
        this.AutoPlay = autoPlay;

        LoadUrl(string.Format(mEmbedUrl, videoId, mAllowAutomaticNativeFullscreen, this.Context.PackageName));
    }

    public void HideVideoView()
    {
        if (this.IsFullScreen)
        {
            if (this.CustomVideoView != null)
            {
                this.CustomVideoView.StopPlayback();
            }

            mRootLayout.RemoveView(mVideoLayout);
            this.ViewCallback.OnCustomViewHidden();

            ((Activity)this.Context).VolumeControlStream = Stream.NotificationDefault;

            this.IsFullScreen = false;
        }
    }

    public void SetupVideoLayout(View video)
    {
        /**
         * As we don't want the touch events to be processed by the underlying WebView, we do not set the WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE flag
         * But then we have to handle directly back press in our View to exit fullscreen.
         * Otherwise the back button will be handled by the topmost Window, id-est the player controller
         */
        mVideoLayout = new DailyMotionFrameLayout(this, this.Context);
        mVideoLayout.SetBackgroundResource(Android.Resource.Color.Black);
        mVideoLayout.AddView(video);

        var lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
        lp.Gravity = GravityFlags.Center;

        mRootLayout.AddView(mVideoLayout, lp);

        this.IsFullScreen = true;
    }

    public void HandleBackPress(Activity activity)
    {
        if (this.IsFullScreen)
        {
            HideVideoView();
        }
        else
        {
            LoadUrl(string.Empty); //Hack to stop video
            activity.Finish();
        }
    }

    public void SetAllowAutomaticNativeFullscreen(bool allowAutomaticNativeFullscreen)
    {
        mAllowAutomaticNativeFullscreen = allowAutomaticNativeFullscreen;
    }
}

Cette classe s’appuie sur 3 autres, représentant les objets nécessaires à la bonne exécution et au bon affichage de la vue:

public class DailyMotionWebChromeClient : WebChromeClient, MediaPlayer.IOnCompletionListener
{
    private readonly DailyMotionWebVideoView _dailyMotionWebVideoView;
    private readonly Context _context;

    public DailyMotionWebChromeClient(DailyMotionWebVideoView dailyMotionWebVideoView, Context context)
    {
        _dailyMotionWebVideoView = dailyMotionWebVideoView;
        _context = context;
    }

    public override View VideoLoadingProgressView
    {
        get
        {
            var pb = new ProgressBar(_context);
            pb.Indeterminate = true;

            return pb;
        }
    }

    public override void OnShowCustomView(View view, ICustomViewCallback callback)
    {
        base.OnShowCustomView(view, callback);

        ((Activity)_context).VolumeControlStream = Stream.Music;
        
        _dailyMotionWebVideoView.IsFullScreen = true;
        _dailyMotionWebVideoView.ViewCallback = callback;

        var layout = view as FrameLayout;
        if (layout != null)
        {
            var frame = layout;
            var child = frame.FocusedChild as VideoView;
            if (child != null)
            { 
                //We are in 2.3
                var video = child;
                frame.RemoveView(video);

                _dailyMotionWebVideoView.SetupVideoLayout(video);

                _dailyMotionWebVideoView.CustomVideoView = video;
                _dailyMotionWebVideoView.CustomVideoView.SetOnCompletionListener(this);
            }
            else
            {
                //Handle 4.x
                _dailyMotionWebVideoView.SetupVideoLayout(view);
            }
        }
    }

    public override void OnHideCustomView()
    {
        base.OnHideCustomView();

        _dailyMotionWebVideoView.HideVideoView();
    }

    public void OnCompletion(MediaPlayer mp)
    {
        _dailyMotionWebVideoView.HideVideoView();
    }
}

public class DailyMotionWebViewClient : WebViewClient
{
    private readonly DailyMotionWebVideoView _dailyMotionWebVideoView;

    public DailyMotionWebViewClient(DailyMotionWebVideoView dailyMotionWebVideoView)
    {
        _dailyMotionWebVideoView = dailyMotionWebVideoView;
    }

    public override bool ShouldOverrideUrlLoading(WebView view, string url)
    {
        var uri = Android.Net.Uri.Parse(url);
        if (uri.Scheme.Equals("dmevent"))
        {
            var @event = uri.GetQueryParameter("event");
            if (@event.Equals("apiready"))
            {
                if (_dailyMotionWebVideoView.AutoPlay)
                {
                    _dailyMotionWebVideoView.CallPlayerMethod("play");
                }
            }
            return true;
        }

        return base.ShouldOverrideUrlLoading(view, url);
    }
}

public class DailyMotionFrameLayout : FrameLayout
{
    private readonly DailyMotionWebVideoView _dailyMotionWebVideoView;

    #region Constructors

    public DailyMotionFrameLayout(IntPtr javaReference, JniHandleOwnership transfer)
        : base(javaReference, transfer)
    {
    }

    public DailyMotionFrameLayout(Context context)
        : base(context)
    {
    }

    public DailyMotionFrameLayout(Context context, IAttributeSet attrs)
        : base(context, attrs)
    {
    }

    public DailyMotionFrameLayout(Context context, IAttributeSet attrs, int defStyleAttr)
        : base(context, attrs, defStyleAttr)
    {
    }

    public DailyMotionFrameLayout(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes)
        : base(context, attrs, defStyleAttr, defStyleRes)
    {
    }

    public DailyMotionFrameLayout(DailyMotionWebVideoView dailyMotionWebVideoView, Context context)
        : base(context)
    {
        _dailyMotionWebVideoView = dailyMotionWebVideoView;
    }

    #endregion

    public override bool DispatchKeyEvent(KeyEvent e)
    {
        if (e.KeyCode == Keycode.Back && e.Action == KeyEventActions.Up)
        {
            _dailyMotionWebVideoView.HideVideoView();
            
            return true;
        }

        return base.DispatchKeyEvent(e);
    }
}

A présent, il ne reste plus qu’à utiliser le player dans votre vue Android:

<TestDailymotionXamarin.DailyMotionWebVideoView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/dmWebVideoView" />

Maintenant, il reste à utiliser les méthodes permettant de charger la vidéo (par son id), gérer l’appui sur le bouton Back (pour quitter le plein écran si nécessaire), etc.:

private DailyMotionWebVideoView _dmView;

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    // Set our view from the "main" layout resource
    SetContentView(Resource.Layout.Main);

    _dmView = this.FindViewById<DailyMotionWebVideoView>(Resource.Id.dmWebVideoView);
    _dmView.SetVideoId("x10iisk");
}

public override void OnBackPressed()
{
    base.OnBackPressed();

    _dmView.HandleBackPress(this);
}

Et le tour est joué! Smile

Attention à bien modifier le fichier manifest de votre application pour rajouter les propriétés/permissions suivantes:

android:hardwareAccelerated="true"

<uses-permission android:name="android.permission.INTERNET" />

Si vous le souhaitez, vous pouvez récupérer le code source directement sur mon Github: https://github.com/ThomasLebrun/DailymotionXamarinAndroidSdk

 

Happy coding!

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus