访问设备的视频库Accessing the device's video library

下载示例 下载示例Download Sample Download the sample

大多数现代移动设备和台式电脑都能够使用其设备的摄像头录制视频。Most modern mobile devices and desktop computers have the ability to record videos using the device's camera. 用户创建的视频之后将以文件形式存储在设备上。The videos that a user creates are then stored as files on the device. 可从图片库中检索这些文件,并通过 VideoPlayer 类像任何其他视频一样播放它们。These files can be retrieved from the image library and played by the VideoPlayer class just like any other video.

照片选取器依赖项服务The photo picker dependency service

每个平台都包含允许用户从设备图片库中选择照片或视频的工具。Each of the platforms includes a facility that allows the user to select a photo or video from the device's image library. 从设备图片库播放视频的第一步是构建在每个平台上调用该图片选取器的依赖项服务。The first step in playing a video from the device's image library is building a dependency service that invokes the image picker on each platform. 下面描述的依赖项服务非常类似于从图片库选取照片一文中所描述的依赖项服务,除了视频选取器返回的是文件名,而不是 Stream 对象 。The dependency service described below is very similar to one defined in the Picking a Photo from the Picture Library article, except that the video picker returns a filename rather than a Stream object.

.NET Standard 库项目为依赖项服务定义名为 IVideoPicker 的接口:The .NET Standard library project defines an interface named IVideoPicker for the dependency service:

namespace FormsVideoLibrary
{
    public interface IVideoPicker
    {
        Task<string> GetVideoFileAsync();
    }
}

每个平台都包含实现此接口的 VideoPicker 类。Each of the platforms contains a class named VideoPicker that implements this interface.

iOS 视频选取器The iOS video picker

iOS VideoPicker 使用 iOS UIImagePickerController 访问图片库,并指定在 iOS MediaType 属性中其应限制为视频(称为“电影”)。The iOS VideoPicker uses the iOS UIImagePickerController to access the image library, specifying that it should be restricted to videos (referred to as "movies") in the iOS MediaType property. 请注意,VideoPicker 显式实现 IVideoPicker 接口。Notice that VideoPicker explicitly implements the IVideoPicker interface. 还要注意将此类标识为依赖项服务的 Dependency 属性。Notice also the Dependency attribute that identifies this class as a dependency service. 这是 Xamarin.Forms 在平台项目中找到依赖项服务的两个要求:These are the two requirements that allow Xamarin.Forms to find the dependency service in the platform project:

using System;
using System.Threading.Tasks;
using UIKit;
using Xamarin.Forms;

[assembly: Dependency(typeof(FormsVideoLibrary.iOS.VideoPicker))]

namespace FormsVideoLibrary.iOS
{
    public class VideoPicker : IVideoPicker
    {
        TaskCompletionSource<string> taskCompletionSource;
        UIImagePickerController imagePicker;

        public Task<string> GetVideoFileAsync()
        {
            // Create and define UIImagePickerController
            imagePicker = new UIImagePickerController
            {
                SourceType = UIImagePickerControllerSourceType.SavedPhotosAlbum,
                MediaTypes = new string[] { "public.movie" }
            };

            // Set event handlers
            imagePicker.FinishedPickingMedia += OnImagePickerFinishedPickingMedia;
            imagePicker.Canceled += OnImagePickerCancelled;

            // Present UIImagePickerController;
            UIWindow window = UIApplication.SharedApplication.KeyWindow;
            var viewController = window.RootViewController;
            viewController.PresentModalViewController(imagePicker, true);

            // Return Task object
            taskCompletionSource = new TaskCompletionSource<string>();
            return taskCompletionSource.Task;
        }

        void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)
        {
            if (args.MediaType == "public.movie")
            {
                taskCompletionSource.SetResult(args.MediaUrl.AbsoluteString);
            }
            else
            {
                taskCompletionSource.SetResult(null);
            }
            imagePicker.DismissModalViewController(true);
        }

        void OnImagePickerCancelled(object sender, EventArgs args)
        {
            taskCompletionSource.SetResult(null);
            imagePicker.DismissModalViewController(true);
        }
    }
}

Android 视频选取器The Android video picker

IVideoPicker 的 Android 实现需要回调方法,该回调方法是应用程序活动的一部分。The Android implementation of IVideoPicker requires a callback method that is part of the application's activity. 因此,MainActivity 类定义两个属性,即字段和回调方法:For that reason, the MainActivity class defines two properties, a field, and a callback method:

namespace VideoPlayerDemos.Droid
{
    ···
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            Current = this;
            ···
        }

        // Field, properties, and method for Video Picker
        public static MainActivity Current { private set; get; }

        public static readonly int PickImageId = 1000;

        public TaskCompletionSource<string> PickImageTaskCompletionSource { set; get; }

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);

            if (requestCode == PickImageId)
            {
                if ((resultCode == Result.Ok) && (data != null))
                {
                    // Set the filename as the completion of the Task
                    PickImageTaskCompletionSource.SetResult(data.DataString);
                }
                else
                {
                    PickImageTaskCompletionSource.SetResult(null);
                }
            }
        }
    }
}

MainActivity 中的 OnCreate 方法将自己的实例存储在静态 Current 属性中。The OnCreate method in MainActivity stores its own instance in the static Current property. 这使得 IVideoPicker 的实现可以获得 MainActivity 实例,以启动“选择视频”选择器 :This allows the implementation of IVideoPicker to obtain the MainActivity instance for starting the Select Video chooser:

using System;
using System.Threading.Tasks;
using Android.Content;
using Xamarin.Forms;

// Need application's MainActivity
using VideoPlayerDemos.Droid;

[assembly: Dependency(typeof(FormsVideoLibrary.Droid.VideoPicker))]

namespace FormsVideoLibrary.Droid
{
    public class VideoPicker : IVideoPicker
    {
        public Task<string> GetVideoFileAsync()
        {
            // Define the Intent for getting images
            Intent intent = new Intent();
            intent.SetType("video/*");
            intent.SetAction(Intent.ActionGetContent);

            // Get the MainActivity instance
            MainActivity activity = MainActivity.Current;

            // Start the picture-picker activity (resumes in MainActivity.cs)
            activity.StartActivityForResult(
                Intent.CreateChooser(intent, "Select Video"),
                MainActivity.PickImageId);

            // Save the TaskCompletionSource object as a MainActivity property
            activity.PickImageTaskCompletionSource = new TaskCompletionSource<string>();

            // Return Task object
            return activity.PickImageTaskCompletionSource.Task;
        }
    }
}

添加到 MainActivity 对象的部分是 VideoPlayerDemos 解决方案中惟一的代码,在该方案中需更改常规应用程序代码以支持 FormsVideoLibrary 类 。The additions to the MainActivity object are the only code in the VideoPlayerDemos solution where normal application code needs to be altered to support the FormsVideoLibrary classes.

UWP 视频选取器The UWP video picker

IVideoPicker 接口的 UWP 实现使用 UWP FileOpenPickerThe UWP implementation of the IVideoPicker interface uses the UWP FileOpenPicker. 选取器从图片库开始搜索文件,并将文件类型限制为 MP4 和 WMV(Windows Media 视频):It begins the file search with the pictures library, and restricts the file types to MP4 and WMV (Windows Media Video):

using System;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Pickers;
using Xamarin.Forms;

[assembly: Dependency(typeof(FormsVideoLibrary.UWP.VideoPicker))]

namespace FormsVideoLibrary.UWP
{
    public class VideoPicker : IVideoPicker
    {
        public async Task<string> GetVideoFileAsync()
        {
            // Create and initialize the FileOpenPicker
            FileOpenPicker openPicker = new FileOpenPicker
            {
                ViewMode = PickerViewMode.Thumbnail,
                SuggestedStartLocation = PickerLocationId.PicturesLibrary
            };

            openPicker.FileTypeFilter.Add(".wmv");
            openPicker.FileTypeFilter.Add(".mp4");

            // Get a file and return the path
            StorageFile storageFile = await openPicker.PickSingleFileAsync();
            return storageFile?.Path;
        }
    }
}

调用依赖项服务Invoking the dependency service

VideoPlayerDemos 程序的“播放库视频”页面演示如何使用视频选取器依赖项服务 。The Play Library Video page of the VideoPlayerDemos program demonstrates how to use the video picker dependency service. XAML 文件包含 VideoPlayer 实例和标记为“显示视频库”的 ButtonThe XAML file contains a VideoPlayer instance and a Button labeled Show Video Library:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:video="clr-namespace:FormsVideoLibrary"
             x:Class="VideoPlayerDemos.PlayLibraryVideoPage"
             Title="Play Library Video">
    <StackLayout>
        <video:VideoPlayer x:Name="videoPlayer"
                           VerticalOptions="FillAndExpand" />

        <Button Text="Show Video Library"
                Margin="10"
                HorizontalOptions="Center"
                Clicked="OnShowVideoLibraryClicked" />
    </StackLayout>
</ContentPage>

代码隐藏文件包含 ButtonClicked 处理程序。The code-behind file contains the Clicked handler for the Button. 调用依赖项服务需要调用 DependencyService.Get 来获取平台项目中 IVideoPicker 接口的实现。Invoking the dependency service requires a call to DependencyService.Get to obtain the implementation of an IVideoPicker interface in the platform project. 然后在该实例上调用 GetVideoFileAsync 方法:The GetVideoFileAsync method is then called on that instance:

namespace VideoPlayerDemos
{
    public partial class PlayLibraryVideoPage : ContentPage
    {
        public PlayLibraryVideoPage()
        {
            InitializeComponent();
        }

       async void OnShowVideoLibraryClicked(object sender, EventArgs args)
        {
            Button btn = (Button)sender;
            btn.IsEnabled = false;

            string filename = await DependencyService.Get<IVideoPicker>().GetVideoFileAsync();

            if (!String.IsNullOrWhiteSpace(filename))
            {
                videoPlayer.Source = new FileVideoSource
                {
                    File = filename
                };
            }

            btn.IsEnabled = true;
        }
    }
}

然后,Clicked 处理程序使用该文件名创建 FileVideoSource 对象,并将其设置为 VideoPlayerSource 属性。The Clicked handler then uses that filename to create a FileVideoSource object and to set it to the Source property of the VideoPlayer.

每个 VideoPlayerRenderer 类都包含类型为 FileVideoSource 的对象的 SetSource 方法中的代码。Each of the VideoPlayerRenderer classes contains code in its SetSource method for objects of type FileVideoSource. 如下所示:These are shown below:

处理 iOS 文件Handling iOS files

iOS 版本的 VideoPlayerRenderer 通过使用带有文件名的静态 Asset.FromUrl 方法处理 FileVideoSource 对象。The iOS version of VideoPlayerRenderer processes FileVideoSource objects by using the static Asset.FromUrl method with the filename. 这会创建一个 AVAsset 对象,表示设备图片库中的文件:This create an AVAsset object representing the file in the device's image library:

namespace FormsVideoLibrary.iOS
{
    public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
    {
        ···
        void SetSource()
        {
            AVAsset asset = null;
            ···
            else if (Element.Source is FileVideoSource)
            {
                string uri = (Element.Source as FileVideoSource).File;

                if (!String.IsNullOrWhiteSpace(uri))
                {
                    asset = AVAsset.FromUrl(new NSUrl(uri));
                }
            }
            ···
        }
        ···
    }
}

处理 Android 文件Handling Android files

处理类型为 FileVideoSource 的对象时,VideoPlayerRenderer 的 Android 实现使用 VideoViewSetVideoPath 方法指定设备图片库中的文件:When processing objects of type FileVideoSource, the Android implementation of VideoPlayerRenderer uses the SetVideoPath method of VideoView to specify the file in the device's image library:

namespace FormsVideoLibrary.Droid
{
    public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, ARelativeLayout>
    {
        ···
        void SetSource()
        {
            isPrepared = false;
            bool hasSetSource = false;
            ···
            else if (Element.Source is FileVideoSource)
            {
                string filename = (Element.Source as FileVideoSource).File;

                if (!String.IsNullOrWhiteSpace(filename))
                {
                    videoView.SetVideoPath(filename);
                    hasSetSource = true;
                }
            }
            ···
        }
        ···
    }
}

处理 UWP 文件Handling UWP files

处理类型为 FileVideoSource 的对象时,SetSource 方法的 UWP 实现需要创建 StorageFile 对象、打开该文件进行读取并将流对象传递给 MediaElementSetSource 方法:When handling objects of type FileVideoSource, the UWP implementation of the SetSource method needs to create a StorageFile object, open that file for reading, and pass the stream object to the SetSource method of the MediaElement:

namespace FormsVideoLibrary.UWP
{
    public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, MediaElement>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
        {
            ···
        async void SetSource()
        {
            bool hasSetSource = false;
            ···
            else if (Element.Source is FileVideoSource)
            {
                // Code requires Pictures Library in Package.appxmanifest Capabilities to be enabled
                string filename = (Element.Source as FileVideoSource).File;

                if (!String.IsNullOrWhiteSpace(filename))
                {
                    StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename);
                    IRandomAccessStreamWithContentType stream = await storageFile.OpenReadAsync();
                    Control.SetSource(stream, storageFile.ContentType);
                    hasSetSource = true;
                }
            }
            ···
        }
        ···
    }
}

对于每个平台,由于文件就在设备上,不需要下载,因此视频几乎可在视频源设置完成后立即开始播放。For each platform, the video begins playing almost immediately after the video source is set because the file is on the device and doesn't need to be downloaded.