#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
при клонировании потока.