Поделиться через


Трансляция мультимедиа

В этой статье показано, как транслировать медиа на удаленные устройства из приложения WinUI.

Встроенная трансляция медиаконтента с использованием MediaPlayerElement

Самый простой способ приведения мультимедиа из приложения WinUI — использовать встроенную возможность приведения элемента управления MediaPlayerElement .

В XAML-файле вашего приложения добавьте MediaPlayerElement и установите AreTransportControlsEnabled в true.

<MediaPlayerElement Name="mediaPlayerElement"  MinHeight="100" MaxWidth="600" HorizontalAlignment="Stretch" AreTransportControlsEnabled="True"/>

Добавьте кнопку, чтобы разрешить пользователю инициировать выбор файла.

<Button x:Name="bOpenButton" Click="bOpenButton_Click" Content="Open"/>

В обработчике событий Click для кнопки создайте новый экземпляр FileOpenPicker, добавьте типы видеофайлов в коллекцию FileTypeFilter и задайте начальное расположение библиотеки видео пользователя.

Вызовите pickSingleFileAsync, чтобы запустить диалоговое окно выбора файлов. При возврате этого метода результатом является объект StorageFile , представляющий видеофайл. Убедитесь, что файл не равен null; если пользователь отменяет операцию выбора, это приведет к тому, что файл будет null. Вызовите метод OpenAsync файла, чтобы получить IRandomAccessStream для файла. Наконец, создайте объект MediaSource из выбранного файла, вызвав CreateFromStorageFile и назначив его свойству Source объекта MediaPlayerElement, чтобы сделать видеофайл источником видео для элемента управления.

private async void bOpenButton_Click(object sender, RoutedEventArgs e)
{
    //Create a new picker
    var filePicker = new Microsoft.Windows.Storage.Pickers.FileOpenPicker(this.AppWindow.Id)
    {
        SuggestedStartLocation = PickerLocationId.VideosLibrary,
        FileTypeFilter = { ".wmv", ".mp4", ".mkv" },
    };

    //Retrieve file from picker
    var result = await filePicker.PickSingleFileAsync();

    if (result is not null)
    {
        var storageFile = await Windows.Storage.StorageFile.GetFileFromPathAsync(result.Path);
        mediaPlayerElement.Source = MediaSource.CreateFromStorageFile(storageFile);
        mediaPlayerElement.MediaPlayer.Play();
    }
    
}

После загрузки видео в MediaPlayerElement пользователь может просто нажать кнопку трансляции в элементах управления, чтобы запустить встроенное диалоговое окно, позволяющее выбрать устройство, на которое будет трансляция загруженного носителя.

Кнопка приведения мультимедиа

Трансляция мультимедиа с помощью CastingDevicePicker

Второй способ передать медиаконтент на устройство — использовать CastingDevicePicker. Сначала объявите переменную-член для объекта Windows.Media.Casting.CastingDevicePicker .

CastingDevicePicker castingPicker;

Когда ваше окно инициализируется, создайте новый экземпляр инструмента выбора кастинга и задайте свойству Filter значение SupportsVideo, чтобы указать, что кастинговые устройства, перечисленные в инструменте выбора, должны поддерживать видео. Зарегистрируйте обработчик события CastingDeviceSelected, которое возникает при выборе устройства для трансляции.

//Initialize our picker object
castingPicker = new CastingDevicePicker();

//Set the picker to filter to video capable casting devices
castingPicker.Filter.SupportsVideo = true;

//Hook up device selected event
castingPicker.CastingDeviceSelected += CastingPicker_CastingDeviceSelected;

В XAML-файле добавьте кнопку, чтобы разрешить пользователю запускать средство выбора.

<Button x:Name="bCastPickerButton" Content="Cast Button" Click="bCastPickerButton_Click"/>

В обработчике событий Click для кнопки вызовите TransformToVisual , чтобы получить преобразование элемента пользовательского интерфейса относительно другого элемента. В этом примере трансформация — это положение кнопки выбора устройства трансляции относительно визуального корня окна приложения. Вызовите метод Show объекта CastingDevicePicker, чтобы запустить диалоговое окно выбора устройства трансляции. Укажите расположение и размеры кнопки выбора трансляции, чтобы диалог появлялся из нажатой пользователем кнопки.

private void bCastPickerButton_Click(object sender, RoutedEventArgs e)
{
    //Retrieve the location of the casting button
    GeneralTransform transform = bCastPickerButton.TransformToVisual(this.Content as UIElement);
    Point pt = transform.TransformPoint(new Point(0, 0));

    //Show the picker above our casting button
    castingPicker.Show(new Rect(pt.X, pt.Y, bCastPickerButton.ActualWidth, bCastPickerButton.ActualHeight),
        Windows.UI.Popups.Placement.Above);
}

В обработчике событий CastingDeviceSelected вызовите метод CreateCastingConnection свойства SelectedCastingDevice события args, который представляет устройство приведения, выбранное пользователем. Регистрируйте обработчики для событий ErrorOccurred и StateChanged . Наконец, вызовите RequestStartCastingAsync, чтобы начать трансляцию, передав результат методу GetAsCastingSource объекта MediaPlayer элемента управления MediaPlayerElement, чтобы указать, что мультимедиа, которое нужно транслировать, является содержимым MediaPlayer, связанного с MediaPlayerElement.

Замечание

Подключение для трансляции должно быть инициировано в потоке пользовательского интерфейса. Так как castingDeviceSelected не вызывается в потоке пользовательского интерфейса, необходимо поместить эти вызовы в вызов DispatcherQueue.TryEnqueue , который вызывает их в потоке пользовательского интерфейса.

private void CastingPicker_CastingDeviceSelected(CastingDevicePicker sender, CastingDeviceSelectedEventArgs args)
{
    //Casting must occur from the UI thread.  This dispatches the casting calls to the UI thread.
   DispatcherQueue.TryEnqueue( async () =>
    {
        //Create a casting conneciton from our selected casting device
        CastingConnection connection = args.SelectedCastingDevice.CreateCastingConnection();

        //Hook up the casting events
        connection.ErrorOccurred += Connection_ErrorOccurred;
        connection.StateChanged += Connection_StateChanged;

        //Cast the content loaded in the media element to the selected casting device
        await connection.RequestStartCastingAsync(mediaPlayerElement.MediaPlayer.GetAsCastingSource());
    });
}

В обработчиках событий ErrorOccurred и StateChanged необходимо обновить пользовательский интерфейс, чтобы информировать пользователя о текущем процессе передачи. Эти события подробно рассматриваются в следующем разделе о создании настраиваемого селектора устройств для кастинга.

private void Connection_StateChanged(CastingConnection sender, object args)
{
    DispatcherQueue.TryEnqueue( () =>
    {
        ShowMessageToUser("Casting Connection State Changed: " + sender.State);
    });
}

private void Connection_ErrorOccurred(CastingConnection sender, CastingConnectionErrorOccurredEventArgs args)
{
    DispatcherQueue.TryEnqueue(() =>
    {
        ShowMessageToUser("Casting Connection State Changed: " + sender.State);
    });
}

Приведение мультимедиа с помощью настраиваемого средства выбора устройств

В следующем разделе описывается, как создать собственный интерфейс выбора устройств трансляции, перечисляя устройства трансляции и устанавливая подключение с помощью вашего кода.

Добавьте следующие элементы управления в вашу XAML-страницу для реализации элементарного пользовательского интерфейса для этого примера.

  • Кнопка для запуска наблюдателя устройств, который ищет доступные устройства трансляции.
  • Элемент управления ProgressRing для отображения пользователю, что идет процесс перечисления.
  • Список обнаруженных устройств трансляции. Определите элемент управления ItemTemplate , чтобы можно было назначить объекты устройства приведения непосредственно элементу управления и отобразить свойство FriendlyName .
  • Кнопка, позволяющая пользователю отключить устройство трансляции.
<Button x:Name="bStartWatcherButton" Content="Watcher Button" Click="bStartWatcherButton_Click"/>
<ProgressRing x:Name="prWatcherProgressRing" IsActive="False"/>
<ListBox x:Name="lbCastingDevicesListBox" MaxWidth="300" HorizontalAlignment="Left" SelectionChanged="lbCastingDevicesListBox_SelectionChanged">
    <!--Listbox content is bound to the FriendlyName field of our casting devices-->
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=FriendlyName}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
<Button x:Name="bDisconnectButton" Content="Disconnect" Click="bDisconnectButton_Click" Visibility="Collapsed"/>

В коде позади объявите переменные-члены для DeviceWatcher и CastingConnection.

DeviceWatcher deviceWatcher;
CastingConnection castingConnection;

В обработчике click для startWatcherButton сначала обновите пользовательский интерфейс, отключив кнопку и активируя кольцо выполнения во время перечисления устройств. Очистите список устройств трансляции.

Затем создайте наблюдатель за устройствами, вызвав DeviceInformation.CreateWatcher. Этот метод можно использовать для просмотра множества различных типов устройств. Укажите, что необходимо отслеживать устройства, поддерживающие приведение видео с помощью строки селектора устройств, возвращаемой CastingDevice.GetDeviceSelector.

Наконец, зарегистрируйте обработчики событий для Added, Удалено, Завершение перечисления и Остановлено.

private void bStartWatcherButton_Click(object sender, RoutedEventArgs e)
{
    bStartWatcherButton.IsEnabled = false;
    prWatcherProgressRing.IsActive = true;

    lbCastingDevicesListBox.Items.Clear();

    //Create our watcher and have it find casting devices capable of video casting
    deviceWatcher = DeviceInformation.CreateWatcher(CastingDevice.GetDeviceSelector(CastingPlaybackTypes.Video));

    //Register for watcher events
    deviceWatcher.Added += DeviceWatcher_Added; ;
    deviceWatcher.Removed += DeviceWatcher_Removed; ;
    deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted; ;
    deviceWatcher.Stopped += DeviceWatcher_Stopped; ;

    //Start the watcher
    deviceWatcher.Start();
}

Событие "Добавлено" возникает при обнаружении нового устройства наблюдателем. В обработчике этого события создайте новый объект CastingDevice, вызвав CastingDevice.FromIdAsync и передав идентификатор обнаруженного устройства трансляции, который содержится в объекте DeviceInformation, переданном в обработчик.

Добавьте CastingDevice в список ListBox, чтобы пользователь мог его выбрать. Из-за ItemTemplate, заданного в XAML, свойство FriendlyName будет использоваться в качестве текста элемента в списке. Так как этот обработчик событий не вызывается в потоке пользовательского интерфейса, необходимо обновить пользовательский интерфейс вызовом DispatcherQueue.TryEnqueue.

private void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    DispatcherQueue.TryEnqueue(async () =>
    {
        //Add each discovered device to our listbox
        CastingDevice addedDevice = await CastingDevice.FromIdAsync(args.Id);
        lbCastingDevicesListBox.Items.Add(addedDevice);
    });
}

Событие "Удалено" возникает, когда монитор обнаруживает, что устройство трансляции больше не доступно. Сравните свойство ID объекта Added, переданного в обработчик, с ID каждого Added в коллекции Items list box. Если идентификатор совпадает, удалите этот объект из коллекции. Опять же, так как пользовательский интерфейс обновляется, этот вызов должен выполняться из вызова RunAsync .

private void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
    DispatcherQueue.TryEnqueue( () =>
    {
        foreach (CastingDevice currentDevice in lbCastingDevicesListBox.Items)
        {
            if (currentDevice.Id == args.Id)
            {
                lbCastingDevicesListBox.Items.Remove(currentDevice);
            }
        }
    });
}

Событие EnumerationCompleted возникает, когда наблюдатель завершит обнаружение устройств. В обработчике этого события обновите пользовательский интерфейс, чтобы пользователь знал, что перечисление устройств завершено, и остановите обозреватель устройств, вызовите Stop.

private void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object args)
{
    DispatcherQueue.TryEnqueue(() =>
    {
        //If enumeration completes, update UI and transition watcher to the stopped state
        ShowMessageToUser("Watcher completed enumeration of devices");
        deviceWatcher.Stop();
    });
}

Событие "Остановлено" вызывается, когда наблюдатель устройства завершает остановку. В обработчике этого события остановите элемент управления ProgressRing и включите startWatcherButton, чтобы пользователь мог перезапустить процесс перечисления устройств.


private void DeviceWatcher_Stopped(DeviceWatcher sender, object args)
{
   DispatcherQueue.TryEnqueue( () =>
    {
        //Update UX when the watcher stops
        bStartWatcherButton.IsEnabled = true;
        prWatcherProgressRing.IsActive = false;
    });
}

Когда пользователь выбирает одно из устройств трансляции из списка, вызывается событие SelectionChanged. В этом обработчике будет создано соединение для трансляции и начнется трансляция.

Во-первых, убедитесь, что служба наблюдения за устройствами остановлена, чтобы обнаружение и перечисление устройств не мешало трансляции мультимедиа. Создайте подключение трансляции, вызвав CreateCastingConnection для объекта CastingDevice, выбранного пользователем. Добавьте обработчики событий для событий StateChanged и ErrorOccurred .

Запустите трансляцию мультимедиа, вызвав RequestStartCastingAsync и передав в него источник трансляции, возвращенный вызовом метода MediaPlayerGetAsCastingSource. Наконец, сделайте кнопку отключения видимой, чтобы позволить пользователю остановить трансляцию мультимедиа.

private async void lbCastingDevicesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (lbCastingDevicesListBox.SelectedItem != null)
    {
        //When a device is selected, first thing we do is stop the watcher so it's search doesn't conflict with streaming
        if (deviceWatcher.Status != DeviceWatcherStatus.Stopped)
        {
            deviceWatcher.Stop();
        }

        //Create a new casting connection to the device that's been selected
        castingConnection = ((CastingDevice)lbCastingDevicesListBox.SelectedItem).CreateCastingConnection();

        //Register for events
        castingConnection.ErrorOccurred += CastingConnection_ErrorOccurred; ;
        castingConnection.StateChanged += CastingConnection_StateChanged; ;

        //Cast the loaded video to the selected casting device.
        await castingConnection.RequestStartCastingAsync(mediaPlayerElement.MediaPlayer.GetAsCastingSource());
        bDisconnectButton.Visibility = Visibility.Visible;
    }
}

В обработчике измененного состояния действие зависит от нового состояния подключения трансляции:

  • Если состояние Подключено или Отрисовка, убедитесь, что элемент управления ProgressRing неактивен, и кнопка отключения видна.
  • Если состояние Отключено, отмените выбор текущего устройства трансляции в списке, сделайте элемент управления ProgressRing неактивным и скрыть кнопку отключения.
  • Если состояние подключено, сделайте элемент управления ProgressRing активным и скройте кнопку отключения.
  • Если состояние отключено, сделайте элемент управления ProgressRing активным и скройте кнопку отключения.
private void CastingConnection_StateChanged(CastingConnection sender, object args)
{
    DispatcherQueue.TryEnqueue( () =>
    {
        //Update the UX based on the casting state
        if (sender.State == CastingConnectionState.Connected || sender.State == CastingConnectionState.Rendering)
        {
            bDisconnectButton.Visibility = Visibility.Visible;
            prWatcherProgressRing.IsActive = false;
        }
        else if (sender.State == CastingConnectionState.Disconnected)
        {
            bDisconnectButton.Visibility = Visibility.Collapsed;
            lbCastingDevicesListBox.SelectedItem = null;
            prWatcherProgressRing.IsActive = false;
        }
        else if (sender.State == CastingConnectionState.Connecting)
        {
            bDisconnectButton.Visibility = Visibility.Collapsed;
            ShowMessageToUser("Connecting");
            prWatcherProgressRing.IsActive = true;
        }
        else
        {
            //Disconnecting is the remaining state
            bDisconnectButton.Visibility = Visibility.Collapsed;
            prWatcherProgressRing.IsActive = true;
        }
    });
}

В обработчике события ErrorOccurred обновите пользовательский интерфейс, чтобы сообщить пользователю о возникновении ошибки трансляции и снять выделение с текущего объекта CastingDevice в списке.

private void CastingConnection_ErrorOccurred(CastingConnection sender, CastingConnectionErrorOccurredEventArgs args)
{
    DispatcherQueue.TryEnqueue( () =>
    {
        //Clear the selection in the listbox on an error
        ShowMessageToUser("Casting Error: " + args.Message);
        lbCastingDevicesListBox.SelectedItem = null;
    });
}

Наконец, реализуйте обработчик для кнопки отключения. Остановите приведение мультимедиа и отключите устройство приведения, вызвав метод DisconnectAsync объекта CastingConnection. Этот вызов должен быть отправлен в поток пользовательского интерфейса путем вызова DispatcherQueue.TryEnqueue.

private async void bDisconnectButton_Click(object sender, RoutedEventArgs e)
{
    if (castingConnection != null)
    {
        //When disconnect is clicked, the casting conneciton is disconnected.  The video should return locally to the media element.
        await castingConnection.DisconnectAsync();
    }
}