UWP MediaPlayerElement: реализация пользовательского потока

#uwp

#увп

Вопрос:

Я создал простой проигрыватель для опробования воспроизведения и загрузил в него стандартный файл MP4. Открытие в виде файла в порядке, открытие в виде потока — нет — он зависает при поиске вперед. Никаких сообщений, никаких исключений, просто зависает. Вероятно, я делаю что-то не так в реализации потока, но я не вижу, что именно. Файл представляет собой фильм 1h30m в формате MP4 в кодировке (Шрек).

Открытие в виде файла

 Media.Source = new MediaPlaybackItem(MediaSource.CreateFromStorageFile(file));
  

Открытие в виде потока

 var inp = new HomeMadeStream(file, this);
await inp.Init();
Media.Source = new MediaPlaybackItem(MediaSource.CreateFromStream(inp, "video/mp4"));
  

В обоих случаях Media есть MediaPlayerElement .

Реализация потока

Это должно было послужить первым шагом к моей цели, для которой мне нужен мой собственный источник данных. Таким образом, я должен реализовать его как поток.

 using System;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Streams;


namespace CencPlayer
{
    public sealed class HomeMadeStream : IRandomAccessStream
    {
        #region Fields

        private readonly StorageFile mFile;

        private readonly IUiLogger mLogger;

        private ulong mPosition;

        private ulong mLength;

        private IRandomAccessStreamWithContentType mStream;

        #endregion


        #region Properties

        bool IRandomAccessStream.CanRead => true;

        bool IRandomAccessStream.CanWrite => false;

        ulong IRandomAccessStream.Position => mPosition;

        ulong IRandomAccessStream.Size { get => mLength; set => throw new NotImplementedException(); }

        #endregion


        #region Init and clean-up

        public HomeMadeStream(StorageFile file, IUiLogger logger)
        {
            mFile = file;
            mLogger = logger;
            mLogger.Log($"Opening {file.DisplayName}.");
        }


        private HomeMadeStream(StorageFile file, ulong position, ulong length, IUiLogger logger)
        {
            mFile = file;
            mPosition = position;
            mLength = length;
            mLogger = logger;
        }


        /// <summary>
        /// Needs to be called right after the constructor.
        /// Separated method due to async.
        /// </summary>
        public async Task Init()
        {
            var prop = await mFile.GetBasicPropertiesAsync();
            mLength = prop.Size;
            mLogger.Log($"Length {mLength}.");
        }


        void IDisposable.Dispose()
        {
            mStream.Dispose();
        }

        #endregion


        #region API

        IInputStream IRandomAccessStream.GetInputStreamAt(ulong position)
        {
            mLogger.Log($"Clone stream from position {position}.");
            return new HomeMadeStream(mFile, position, mLength, mLogger);
        }


        IOutputStream IRandomAccessStream.GetOutputStreamAt(ulong position)
        {
            throw new NotImplementedException();
        }


        void IRandomAccessStream.Seek(ulong position)
        {
            if (mPosition == position)
                return;

            mLogger.Log($"Seek from {mPosition} to {position}.");
            mPosition = position;
        }


        IRandomAccessStream IRandomAccessStream.CloneStream()
        {
            mLogger.Log("Clone stream.");
            return new HomeMadeStream(mFile, mPosition, mLength, mLogger);
        }


        IAsyncOperationWithProgress<IBuffer, uint> IInputStream.ReadAsync(IBuffer buffer, uint count, InputStreamOptions options)
        {
            return new AsyncOperationWithProgress<IBuffer>(async () =>
            {
                if (mStream is null)
                {
                    mStream = await mFile.OpenReadAsync();
                }

                mStream.Seek(mPosition);
                mPosition  = count;
                return await mStream.ReadAsync(buffer, count, options);
            });
        }


        IAsyncOperationWithProgress<uint, uint> IOutputStream.WriteAsync(IBuffer buffer)
        {
            throw new NotImplementedException();
        }


        IAsyncOperation<bool> IOutputStream.FlushAsync()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
  

Утилита AsyncOperationWithProgress

Чтобы объединить async/await подход StorageFile и IAsyncOperation потока, этот класс был найден в Интернете.

 internal class AsyncOperationWithProgress<TResult> : IAsyncOperationWithProgress<TResult, uint>
{
    #region Fields

    private readonly Task<TResult> mWorker;

    private AsyncStatus mStatus;

    #endregion


    #region IAsyncOperationWithProgress properties

    /// <summary>
    /// Callback to report operation progress.
    /// </summary>
    AsyncOperationProgressHandler<TResult, uint> IAsyncOperationWithProgress<TResult, uint>.Progress { get; set; }


    /// <summary>
    /// Callback to report operation completion.
    /// </summary>
    AsyncOperationWithProgressCompletedHandler<TResult, uint> IAsyncOperationWithProgress<TResult, uint>.Completed { get; set; }


    Exception IAsyncInfo.ErrorCode => mWorker.Exception;


    uint IAsyncInfo.Id => (uint)mWorker.Id;


    AsyncStatus IAsyncInfo.Status => mStatus;

    #endregion


    #region Init and clean-up

    public AsyncOperationWithProgress(Func<Task<TResult>> workerFn)
    {
        mWorker = workerFn.Invoke();
        mWorker.ContinueWith(task =>
            {
                TResult res = default;

                if (task.IsFaulted)
                {
                    mStatus = AsyncStatus.Error;
                }
                else
                {
                    mStatus = AsyncStatus.Completed;
                    res = task.Resu<
                }

                (this as IAsyncOperationWithProgress<TResult, uint>).Completed?.Invoke(this, mStatus);
                return res;
            });
    }

    #endregion


    #region IAsyncOperationWithProgress API

    TResult IAsyncOperationWithProgress<TResult, uint>.GetResults()
    {
        if (mStatus != AsyncStatus.Completed)
            throw new ArgumentException($"Cannot get result when status is {mStatus}.");

        return mWorker.Resu<
    }


    void IAsyncInfo.Cancel()
    {
        // Do nothing
    }


    void IAsyncInfo.Close()
    {
        // Do nothing
    }

    #endregion
}
  

MainWindow XAML

На всякий случай — это XAML основного окна.

 <Page
    x:Class="CencPlayer.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:CencPlayer"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="3*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <StackPanel Orientation="Horizontal">
            <Button
                HorizontalAlignment="Left"
                Click="OpenFileButton_Click"
                Content="Open as file..." />
            <Button
                HorizontalAlignment="Left"
                Click="OpenStreamButton_Click"
                Content="Open as stream..." />
        </StackPanel>

        <MediaPlayerElement
            x:Name="Media"
            Grid.Row="1"
            AreTransportControlsEnabled="True"
            AutoPlay="True">
            <MediaPlayerElement.TransportControls>
                <MediaTransportControls
                    IsStopButtonVisible="True" IsStopEnabled="True"
                    IsFullWindowButtonVisible="True"
                    IsNextTrackButtonVisible="True"
                    IsPreviousTrackButtonVisible="True"
                    IsSeekBarVisible="True"
                    IsSkipBackwardButtonVisible="True" IsSkipBackwardEnabled="True"
                    IsSkipForwardButtonVisible="True" IsSkipForwardEnabled="True"
                    IsPlaybackRateButtonVisible="True" IsPlaybackRateEnabled="True"
                    IsVolumeButtonVisible="True"
                    IsZoomButtonVisible="True" IsZoomEnabled="True"/>
            </MediaPlayerElement.TransportControls>
        </MediaPlayerElement>

        <TextBlock
            Grid.Row="1"
            Grid.Column="1"
            Text="{x:Bind LogText, Mode=OneWay}" />
    </Grid>
</Page>
  

Код главного окна

Код тривиален.

     public sealed partial class MainPage : Page, IUiLogger
    {
        public static readonly DependencyProperty LogTextProperty = DependencyProperty.Register(
            nameof(LogText), typeof(string), typeof(MainPage), new PropertyMetadata(""));


        public string LogText
        {
            get { return (string)GetValue(LogTextProperty); }
            set { SetValue(LogTextProperty, value); }
        }


        public MainPage()
        {
            InitializeComponent();
        }


        private async void OpenFileButton_Click(object sender, RoutedEventArgs e)
        {
            var dlg = new FileOpenPicker
            {
                SuggestedStartLocation = PickerLocationId.Desktop,
                FileTypeFilter = { "*" },
                CommitButtonText = "Play"
            };
            var file = await dlg.PickSingleFileAsync();
            if (file is null) return;

            Media.Source = new MediaPlaybackItem(MediaSource.CreateFromStorageFile(file));
        }


        private async void OpenStreamButton_Click(object sender, RoutedEventArgs e)
        {
            var dlg = new FileOpenPicker
            {
                SuggestedStartLocation = PickerLocationId.Desktop,
                FileTypeFilter = { "*" },
                CommitButtonText = "Play"
            };
            var file = await dlg.PickSingleFileAsync();
            if (file is null) return;

            var inp = new HomeMadeStream(file, this);
            await inp.Init();
            Media.Source = new MediaPlaybackItem(MediaSource.CreateFromStream(inp, "video/mp4"));
        }


        async void IUiLogger.Log(string text)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
                () => LogText  = Environment.NewLine   text);
        }
    }
  

Комментарии:

1. Могу я узнать, что у вас за IUiLogger? Кроме того, когда я открыл исходный код в виде потока и нажал кнопку «пропустить вперед», он не зависал и воспроизводился хорошо. Можете ли вы предоставить нам простой образец, который можно воспроизвести, и ваш файл mp4 для тестирования?

2. Я исправил проблему, изменив способ Seek использования. ## Добавить поле csharp private ulong mStartPosition; ## В ReadAsync: csharp if (mStream is null) { mStream = await mFile.OpenReadAsync(); mStream.Seek(mStartPosition); } return await mStream.ReadAsync(buffer, count, options); ## В поиске csharp void IRandomAccessStream.Seek(ulong position) { if (mStream is null) mStartPosition = position; else mStream.Seek(position); } и передаче position при клонировании потока.

3. IUILogger это тривиальный интерфейс, реализованный в главном окне: csharp public interface IUiLogger { void Log(string text); }

4. Как уже говорилось, мой рабочий образец — это фильм Шрек в формате MP4, весит около 1 ГБ. Опубликовать здесь невозможно…

Ответ №1:

Я исправил проблему, изменив способ Seek использования.

Добавить поле

 private ulong mStartPosition;
  

Инициализируется в частном конструкторе.

В ReadAsync:

 if (mStream is null)
{
    mStream = await mFile.OpenReadAsync();
    mStream.Seek(mStartPosition);
}

return await mStream.ReadAsync(buffer, count, options);
  

В поиске

 void IRandomAccessStream.Seek(ulong position)
{
    if (mStream is null)
        mStartPosition = position;
    else
        mStream.Seek(position);
}
  

И передача position при клонировании потока.