Xamarin video player unable to play video from sim

2019-08-24 05:08发布

问题:

Hi i implemented the xamarin video player which is described the following link https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/video-player/

i am downloading videos through code to the documents folder of the application which has a path like this [/Users/vaibhavjain/Library/Developer/CoreSimulator/Devices/{GUID}/data/Containers/Data/Application/{GUID}/Documents/MediaDocuments/Nature1.mp4] now i checked that videos are downloaded correctly to this path but when i am passing this path as source to the video player above it is not able to play it

my guess is that the player is not able to reach the path or it is expecting a relative path but i am unable to find any example of the type of path i should provide.

here is the code for custom Renderer on ios

public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
    AVPlayer player;
    AVPlayerItem playerItem;
    AVPlayerViewController _playerViewController;       // solely for ViewController property

    public override UIViewController ViewController => _playerViewController;

    protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
    {
        base.OnElementChanged(args);

        if (args.NewElement != null)
        {
            if (Control == null)
            {
                // Create AVPlayerViewController
                _playerViewController = new AVPlayerViewController();

                // Set Player property to AVPlayer
                player = new AVPlayer();
                _playerViewController.Player = player;

                var x = _playerViewController.View;

                // Use the View from the controller as the native control
                SetNativeControl(_playerViewController.View);
            }

            SetAreTransportControlsEnabled();
            SetSource();

            args.NewElement.UpdateStatus += OnUpdateStatus;
            args.NewElement.PlayRequested += OnPlayRequested;
            args.NewElement.PauseRequested += OnPauseRequested;
            args.NewElement.StopRequested += OnStopRequested;
        }

        if (args.OldElement != null)
        {
            args.OldElement.UpdateStatus -= OnUpdateStatus;
            args.OldElement.PlayRequested -= OnPlayRequested;
            args.OldElement.PauseRequested -= OnPauseRequested;
            args.OldElement.StopRequested -= OnStopRequested;
        }
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        if (player != null)
        {
            player.ReplaceCurrentItemWithPlayerItem(null);
        }
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        base.OnElementPropertyChanged(sender, args);

        if (args.PropertyName == VideoPlayer.AreTransportControlsEnabledProperty.PropertyName)
        {
            SetAreTransportControlsEnabled();
        }
        else if (args.PropertyName == VideoPlayer.SourceProperty.PropertyName)
        {
            SetSource();
        }
        else if (args.PropertyName == VideoPlayer.PositionProperty.PropertyName)
        {
            TimeSpan controlPosition = ConvertTime(player.CurrentTime);

            if (Math.Abs((controlPosition - Element.Position).TotalSeconds) > 1)
            {
                player.Seek(CMTime.FromSeconds(Element.Position.TotalSeconds, 1));
            }
        }
    }

    void SetAreTransportControlsEnabled()
    {
        ((AVPlayerViewController)ViewController).ShowsPlaybackControls = Element.AreTransportControlsEnabled;
    }

    void SetSource()
    {
        AVAsset asset = null;

        if (Element.Source is UriVideoSource)
        {
            string uri = (Element.Source as UriVideoSource).Uri;

            if (!String.IsNullOrWhiteSpace(uri))
            {
                asset = AVAsset.FromUrl(new NSUrl(uri));
            }
        }
        else if (Element.Source is FileVideoSource)
        {
            string uri = (Element.Source as FileVideoSource).File;

            if (!String.IsNullOrWhiteSpace(uri))
            {
                asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(uri, null));
            }
        }
        else if (Element.Source is ResourceVideoSource)
        {
            string path = (Element.Source as ResourceVideoSource).Path;

            if (!String.IsNullOrWhiteSpace(path))
            {
                string directory = Path.GetDirectoryName(path);
                string filename = Path.GetFileNameWithoutExtension(path);
                string extension = Path.GetExtension(path).Substring(1);
                NSUrl url = NSBundle.MainBundle.GetUrlForResource(filename, extension, directory);
                asset = AVAsset.FromUrl(url);
            }
        }

        if (asset != null)
        {
            playerItem = new AVPlayerItem(asset);
        }
        else
        {
            playerItem = null;
        }

        player.ReplaceCurrentItemWithPlayerItem(playerItem);

        if (playerItem != null && Element.AutoPlay)
        {
            player.Play();
        }
    }

    // Event handler to update status
    void OnUpdateStatus(object sender, EventArgs args)
    {
        VideoStatus videoStatus = VideoStatus.NotReady;

        switch (player.Status)
        {
            case AVPlayerStatus.ReadyToPlay:
                switch (player.TimeControlStatus)
                {
                    case AVPlayerTimeControlStatus.Playing:
                        videoStatus = VideoStatus.Playing;
                        break;

                    case AVPlayerTimeControlStatus.Paused:
                        videoStatus = VideoStatus.Paused;
                        break;
                }
                break;
        }
        ((IVideoPlayerController)Element).Status = videoStatus;

        if (playerItem != null)
        {
            ((IVideoPlayerController)Element).Duration = ConvertTime(playerItem.Duration);
            ((IElementController)Element).SetValueFromRenderer(VideoPlayer.PositionProperty, ConvertTime(playerItem.CurrentTime));
        }
    }

    TimeSpan ConvertTime(CMTime cmTime)
    {
        return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds);

    }

    // Event handlers to implement methods
    void OnPlayRequested(object sender, EventArgs args)
    {
        player.Play();
    }

    void OnPauseRequested(object sender, EventArgs args)
    {
        player.Pause();
    }

    void OnStopRequested(object sender, EventArgs args)
    {
        player.Pause();
        player.Seek(new CMTime(0, 1));
    }
}

回答1:

I don't see your code and can't know what is your problem. I would show you how to make it work about play a local video stored in the document folder:

In xamarin.forms:

public class MyVideoView : View
{
}

And in xaml, simply use this MyVideoView :

<StackLayout>
    <!-- Place new controls here -->
    <local:MyVideoView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />

</StackLayout>

In the iOS project, the custom renderer should be:

public class VideoPlayerRenderer : ViewRenderer
{
    AVPlayer player;
    AVPlayerItem playerItem;
    AVPlayerViewController _playerViewController;       // solely for ViewController property

    public override UIViewController ViewController => _playerViewController;


    protected override void OnElementChanged(ElementChangedEventArgs<View> e)
    {
        base.OnElementChanged(e);

        if (e.NewElement != null)
        {
            if (Control == null)
            {
                // Create AVPlayerViewController
                _playerViewController = new AVPlayerViewController();

                var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                var path = Path.Combine(documents, "iOSApiVideo.mp4");

                NSUrl url = NSUrl.CreateFileUrl(path, null);

                //if you put your video in the project directly use below code
                //NSUrl url = NSBundle.MainBundle.GetUrlForResource("iOSApiVideo", "mp4");

                // Set Player property to AVPlayer
                player = new AVPlayer(url);

                _playerViewController.Player = player;

                // Use the View from the controller as the native control
                SetNativeControl(_playerViewController.View);

                player.Play();
            }                
        }
    }     
}

Replace the path with your own path there and it will work.

Note:

Doesn't work

NSUrl url = new NSUrl(path);

Work

NSUrl url = NSUrl.CreateFileUrl(path, null);


回答2:

I am posting the final answer because i ended up using Jack's answer to modify my customRenderer. so i changed this condition in the SetSource() function in custom Renderer

 else if (Element.Source is FileVideoSource)
        {
            string uri = (Element.Source as FileVideoSource).File;

            if (!String.IsNullOrWhiteSpace(uri))
            {
                var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                var path = Path.Combine(documents, uri);

                NSUrl url = NSUrl.CreateFileUrl(path, null);
                asset = AVAsset.FromUrl(url);
            }
        }

the only change is now i am getting the path inside customRenderer and only passing fileName as the parameter from ViewModel. earlier it was full path like [/Users/vaibhavjain/Library/Developer/CoreSimulator/Devices/{GUID}/data/Containers/Data/Application/{GUID}/Documents/MediaDocuments/Nature1.mp4]