媒體傳播Media casting

本文示範如何從通用 Windows app 將媒體傳播到遠端裝置。This article shows you how to cast media to remote devices from a Universal Windows app.

MediaPlayerElement 內建的媒體傳播Built-in media casting with MediaPlayerElement

從通用 Windows app 傳播媒體最簡單的方式是使用 MediaPlayerElement 控制項內建的傳播功能。The simplest way to cast media from a Universal Windows app is to use the built-in casting capability of the MediaPlayerElement control.

若要讓使用者開啟要在 MediaPlayerElement 控制項中播放的視訊檔案,請將下列命名空間加入到您的專案。To allow the user to open a video file to be played in the MediaPlayerElement control, add the following namespaces to your project.

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Media.Core;

在 app 的 XAML 檔案中,加入 MediaPlayerElement 並將 AreTransportControlsEnabled 設定為 true。In your app's XAML file, add a MediaPlayerElement and set AreTransportControlsEnabled to true.

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

新增按鈕讓使用者開始挑選檔案。Add a button to let the user initiate picking a file.

<Button x:Name="openButton" Click="openButton_Click" Content="Open"/>

在按鈕的 Click 事件處理常式中,建立 FileOpenPicker 的新執行個體、將視訊檔案類型加入到 FileTypeFilter 集合,並將開始位置設定為使用者的視訊庫。In the Click event handler for the button, create a new instance of the FileOpenPicker, add video file types to the FileTypeFilter collection, and set the starting location to the user's videos library.

呼叫 PickSingleFileAsync,以啟動檔案選擇器對話方塊。Call PickSingleFileAsync to launch the file picker dialog. 這個方法傳回的結果是一個代表視訊檔案的 StorageFile 物件。When this method returns, the result is a StorageFile object representing the video file. 請確認檔案不是 Null (使用者取消挑選作業時就會是 Null)。Check to make sure the file isn't null, which it will be if the user cancels the picking operation. 呼叫檔案的 OpenAsync 方法,取得檔案的 IRandomAccessStreamCall the file's OpenAsync method to get an IRandomAccessStream for the file. 最後,藉由呼叫 CreateFromStorageFile 並將它指派給 MediaPlayerElement 物件的 Source 屬性,以從選取的檔案建立一個新的 MediaSource 物件,讓該視訊檔案成為控制項的視訊來源。Finally, create a new MediaSource object from the selected file by calling CreateFromStorageFile and assign it to the MediaPlayerElement object's Source property to make the video file the video source for the control.

private async void openButton_Click(object sender, RoutedEventArgs e)
{
    //Create a new picker
    FileOpenPicker filePicker = new FileOpenPicker();

    //Add filetype filters.  In this case wmv and mp4.
    filePicker.FileTypeFilter.Add(".wmv");
    filePicker.FileTypeFilter.Add(".mp4");

    //Set picker start location to the video library
    filePicker.SuggestedStartLocation = PickerLocationId.VideosLibrary;

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

    //If we got a file, load it into the media lement
    if (file != null)
    {
        mediaPlayerElement.Source = MediaSource.CreateFromStorageFile(file);
        mediaPlayerElement.MediaPlayer.Play();
    }
}

MediaPlayerElement 中載入視訊之後,使用者可以在傳輸控制項直接按下傳播按鈕,以啟動內建的對話方塊,讓他們選擇已載入的媒體要傳播到哪個裝置。Once the video is loaded in the MediaPlayerElement, the user can simply press the casting button on the transport controls to launch a built-in dialog that allows them to choose a device to which the loaded media will be cast.

MediaElement 傳播按鈕

注意

從 Windows 10 版本 1607 開始,建議您使用 MediaPlayer 類別來播放媒體項目。Starting with Windows 10, version 1607, it is recommended that you use the MediaPlayer class to play media items. MediaPlayerElement 是輕量型的 XAML 控制項,可用來轉譯 XAML 頁面中的 MediaPlayer 內容。The MediaPlayerElement is a lightweight XAML control that is used to render the content of a MediaPlayer in a XAML page. MediaElement 控制項仍持續受支援,以提供回溯相容性。The MediaElement control continues to be supported for backwards compatibility. 如需使用 MediaPlayerMediaPlayerElement 播放媒體內容的詳細資訊,請參閱使用 MediaPlayer 播放音訊和視訊For more information on using MediaPlayer and MediaPlayerElement to play media content, see Play audio and video with MediaPlayer. 如需使用 MediaSource 和相關 API 的詳細資訊,請參閱媒體項目、播放清單和曲目For information on using MediaSource and related APIs to work with media content, see Media items, playlists, and tracks.

使用 CastingDevicePicker 傳播媒體Media casting with the CastingDevicePicker

將媒體傳播到裝置的第二個方法是使用 CastingDevicePickerA second way to cast media to a device is to use the CastingDevicePicker. 若要使用這個類別,請在專案中加入 Windows.Media.Casting 命名空間。To use this class, include the Windows.Media.Casting namespace in your project.

using Windows.Media.Casting;

宣告 CastingDevicePicker 物件的成員變數。Declare a member variable for the CastingDevicePicker object.

CastingDevicePicker castingPicker;

當頁面初始化之後,建立傳播選擇器的新執行個體,並將 Filter 設定到 SupportsVideo 屬性,指出選擇器所列出的傳播裝置應該支援視訊。When you page is initialized, create a new instance of the casting picker and set the Filter to SupportsVideo property to indicate that the casting devices listed by the picker should support video. 針對使用者挑選傳播的裝置時所引發的 CastingDeviceSelected 事件,註冊處理常式。Register a handler for the CastingDeviceSelected event, which is raised when the user picks a device for casting.

//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 檔案中,新增按鈕讓使用者啟動選擇器。In your XAML file, add a button to allow the user to launch the picker.

<Button x:Name="castPickerButton" Content="Cast Button" Click="castPickerButton_Click"/>

在按鈕的 Click 事件處理常式中,呼叫 TransformToVisual,以取得相對於另一個元素的 UI 元素轉換。In the Click event handler for the button, call TransformToVisual to get the transform of a UI element relative to another element. 在這個範例中,轉換是指傳播選擇器按鈕的位置,相對於應用程式視窗的視覺化根目錄。In this example, the transform is the position of the cast picker button relative to the visual root of the application window. 呼叫 CastingDevicePicker 物件的 Show 方法,以啟動傳播選擇器對話方塊。Call the Show method of the CastingDevicePicker object to launch the casting picker dialog. 指定傳播選擇器按鈕的位置和尺寸,讓系統可以從使用者按下的按鈕帶出對話方塊。Specify the location and dimensions of the cast picker button so that the system can make the dialog fly out from the button that the user pressed.

private void castPickerButton_Click(object sender, RoutedEventArgs e)
{
    //Retrieve the location of the casting button
    GeneralTransform transform = castPickerButton.TransformToVisual(Window.Current.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, castPickerButton.ActualWidth, castPickerButton.ActualHeight),
        Windows.UI.Popups.Placement.Above);
}

CastingDeviceSelected 事件處理常式中,呼叫事件引數的 SelectedCastingDevice 屬性 (代表使用者選取的傳播裝置) 的 CreateCastingConnection 方法。In the CastingDeviceSelected event handler, call the CreateCastingConnection method of the SelectedCastingDevice property of the event args, which represents the casting device selected by the user. 註冊 ErrorOccurredStateChanged 事件的處理常式。Register handlers for the ErrorOccurred and StateChanged events. 最後,呼叫 RequestStartCastingAsync 以開始傳播,並將結果傳入 MediaPlayerElement 控制項的 MediaPlayer 物件的 GetAsCastingSource 方法,以指定要傳播的媒體是與 MediaPlayerElement 關聯的 MediaPlayer 的內容。Finally, call RequestStartCastingAsync to begin casting, passing in the result to the MediaPlayerElement control's MediaPlayer object's GetAsCastingSource method to specify that the media to be cast is the content of the MediaPlayer associated with the MediaPlayerElement.

注意

傳播連線必須在 UI 執行緒上起始。The casting connection must be initiated on the UI thread. 因為UI 執行緒上不會呼叫 CastingDeviceSelected,您必須將這些呼叫放在 CoreDispatcher.RunAsync 的呼叫內,才能在 UI 執行緒上呼叫它們。Since the CastingDeviceSelected is not called on the UI thread, you must place these calls inside a call to CoreDispatcher.RunAsync which causes them to be called on the UI thread.

private async void CastingPicker_CastingDeviceSelected(CastingDevicePicker sender, CastingDeviceSelectedEventArgs args)
{
    //Casting must occur from the UI thread.  This dispatches the casting calls to the UI thread.
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, 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());
    });
}

ErrorOccurredStateChanged 事件處理常式中,您應該更新 UI 讓使用者知道目前的傳播狀態。In the ErrorOccurred and StateChanged event handlers, you should update your UI to inform the user of the current casting status. 下節的建立自訂傳播裝置選擇器中,將詳細討論這些事件。These events are discussed in detail in the following section on creating a custom casting device picker.

private async void Connection_StateChanged(CastingConnection sender, object args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        ShowMessageToUser("Casting Connection State Changed: " + sender.State);
    });
}

private async void Connection_ErrorOccurred(CastingConnection sender, CastingConnectionErrorOccurredEventArgs args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        ShowMessageToUser("Casting Connection State Changed: " + sender.State);
    });
}

使用自訂裝置選擇器來傳播媒體Media casting with a custom device picker

下節說明如何從程式碼中列舉傳播裝置並起始連線,以建立您自己的傳播裝置選擇器 UI。The following section describes how to create your own casting device picker UI by enumerating the casting devices and initiating the connection from your code.

若要列舉可用的傳播裝置,請在專案中加入 Windows.Devices.Enumeration 命名空間。To enumerate the available casting devices, include the Windows.Devices.Enumeration namespace in your project.

using Windows.Devices.Enumeration;

將下列控制項新增到 XAML 頁面,以實作這個範例的初步 UI:Add the following controls to your XAML page to implement the rudimentary UI for this example:

  • 用來啟動裝置監控程式的按鈕,以尋找可用的傳播裝置。A button to start the device watcher that looks for available casting devices.
  • ProgressRing 控制項會提供使用者回饋,讓他們知道傳播列舉正在進行。A ProgressRing control to provide feedback to the user that casting enumeration is ongoing.
  • ListBox,列出找到的傳播裝置。A ListBox to list the discovered casting devices. 定義控制項的 ItemTemplate,讓我們可以將傳播裝置物件直接指派給控制項,且仍然顯示 FriendlyName 屬性。Define an ItemTemplate for the control so that we can assign the casting device objects directly to the control and still display the FriendlyName property.
  • 可讓使用者中斷連接傳播裝置的按鈕。A button to allow the user to disconnect the casting device.
<Button x:Name="startWatcherButton" Content="Watcher Button" Click="startWatcherButton_Click"/>
<ProgressRing x:Name="watcherProgressRing" IsActive="False"/>
<ListBox x:Name="castingDevicesListBox" MaxWidth="300" HorizontalAlignment="Left" SelectionChanged="castingDevicesListBox_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="disconnectButton" Content="Disconnect" Click="disconnectButton_Click" Visibility="Collapsed"/>

在程式碼後置中,宣告 DeviceWatcherCastingConnection 的成員變數。In your code behind, declare member variables for the DeviceWatcher and the CastingConnection.

DeviceWatcher deviceWatcher;
CastingConnection castingConnection;

startWatcherButtonClick 處理常式中,首先更新 UI,將按鈕停用,並在裝置列舉進行時讓進度環開始轉動。In the Click handler for the startWatcherButton, first update the UI by disabling the button and making the progress ring active while device enumeration is ongoing. 清除傳播裝置的清單方塊。Clear the list box of casting devices.

接著,呼叫 DeviceInformation.CreateWatcher,建立裝置監控程式。Next, create a device watcher by calling DeviceInformation.CreateWatcher. 這個方法可以用來監看許多不同類型的裝置。This method can be used to watch for many different types of devices. 使用 CastingDevice.GetDeviceSelector 傳回的裝置選取器字串,指定您要監看支援視訊傳播的裝置。Specify that you want to watch for devices that support video casting by using the device selector string returned by CastingDevice.GetDeviceSelector.

最後,註冊 AddedRemovedEnumerationCompletedStopped 事件的事件處理常式。Finally, register event handlers for the Added, Removed, EnumerationCompleted, and Stopped events.

private void startWatcherButton_Click(object sender, RoutedEventArgs e)
{
    startWatcherButton.IsEnabled = false;
    watcherProgressRing.IsActive = true;

    castingDevicesListBox.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();
}

當監控程式發現新的裝置時會引發 Added 事件。The Added event is raised when a new device is discovered by the watcher. 在這個事件的處理常式中,呼叫 CastingDevice.FromIdAsync,並傳入已發現的傳播裝置的識別碼 (包含在傳給處理常式的 DeviceInformation 物件中),以建立新的 CastingDevice 物件。In the handler for this event, create a new CastingDevice object by calling CastingDevice.FromIdAsync and passing in the ID of the discovered casting device, which is contained in the DeviceInformation object passed into the handler.

CastingDevice 新增到傳播裝置 ListBox,供使用者選取。Add the CastingDevice to the casting device ListBox so that the user can select it. 根據 XAML 中定義的 ItemTemplateFriendlyName 屬性會當做清單方塊中的項目文字。Because of the ItemTemplate defined in the XAML, the FriendlyName property will be used as the item text for in the list box. 因為 UI 執行緒上不會呼叫這個事件處理常式,您必須從 CoreDispatcher.RunAsync 的呼叫內更新 UI。Because this event handler is not called on the UI thread, you must update the UI from within a call to CoreDispatcher.RunAsync.

private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
    {
        //Add each discovered device to our listbox
        CastingDevice addedDevice = await CastingDevice.FromIdAsync(args.Id);
        castingDevicesListBox.Items.Add(addedDevice);
    });
}

當監控程式偵測到傳播裝置已不存在時會引發 Removed 事件。The Removed event is raised when the watcher detects that a casting device is no longer present. 比較傳入處理常式的 Added 物件的 ID 屬性,與清單方塊的 Items 集合中每個 Added 的 ID。Compare the ID property of the Added object passed into the handler to the ID of each Added in the list box's Items collection. 如果識別碼相符,則從集合中移除該物件。If the ID matches, remove that object from the collection. 同樣地,因為是更新 UI,所以必須從 RunAsync 呼叫內執行這個呼叫。Again, because the UI is being updated, this call must be made from within a RunAsync call.

private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        foreach (CastingDevice currentDevice in castingDevicesListBox.Items)
        {
            if (currentDevice.Id == args.Id)
            {
                castingDevicesListBox.Items.Remove(currentDevice);
            }
        }
    });
}

當監控程式完成偵測裝置時會引發 EnumerationCompleted 事件。The EnumerationCompleted event is raised when the watcher has finished detecting devices. 在這個事件的處理常式中,更新 UI 讓使用者知道裝置列舉已完成,並呼叫 Stop 來停止裝置監控程式。In the handler for this event, update the UI to let the user know that device enumeration has completed and stop the device watcher by calling Stop.

private async void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        //If enumeration completes, update UI and transition watcher to the stopped state
        ShowMessageToUser("Watcher completed enumeration of devices");
        deviceWatcher.Stop();
    });
}

當裝置監控程式完成停止時會引發 Stopper 事件。The Stopped event is raised when the device watcher has finished stopping. 在這個事件的處理常式中,停止 ProgressRing 控制項並重新啟用 startWatcherButton,讓使用者可以重新啟動裝置列舉程序。In the handler for this event, stop the ProgressRing control and reenable the startWatcherButton so that the user can restart the device enumeration process.

private async void DeviceWatcher_Stopped(DeviceWatcher sender, object args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        //Update UX when the watcher stops
        startWatcherButton.IsEnabled = true;
        watcherProgressRing.IsActive = false;
    });
}

當使用者從清單方塊選取其中一個傳播裝置時,就會引發 SelectionChanged 事件。When the user selects one of the casting devices from the list box, the SelectionChanged event is raised. 在這個處理常式內,將會建立傳播連線並開始傳播。It is within this handler that the casting connection will be created and casting will be started.

首先,請先確認裝置監控程式已停止,以避免裝置列舉干擾媒體傳播。First, make sure the device watcher is stopped so that device enumeration doesn't interfere with media casting. 在使用者所選取的 CastingDevice 物件上,呼叫 CreateCastingConnection 來建立傳播連線。Create a casting connection by calling CreateCastingConnection on the CastingDevice object selected by the user. 新增 StateChangedErrorOccurred 事件的事件處理常式。Add event handlers for the StateChanged and ErrorOccurred events.

呼叫 RequestStartCastingAsync,並傳入呼叫 MediaPlayer 方法 GetAsCastingSource 所傳回的傳播來源,以開始傳播媒體。Start media casting by calling RequestStartCastingAsync, passing in the casting source returned by calling the MediaPlayer method GetAsCastingSource. 最後,顯示中斷連線按鈕,讓使用者停止媒體傳播。Finally, make the disconnect button visible to allow the user to stop media casting.

private async void castingDevicesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (castingDevicesListBox.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)castingDevicesListBox.SelectedItem).CreateCastingConnection();

        //Register for events
        castingConnection.ErrorOccurred += Connection_ErrorOccurred;
        castingConnection.StateChanged += Connection_StateChanged;

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

在狀態變更處理常式中,您所採取的動作取決於傳播連線的新狀態:In the state changed handler, the action you take depends on the new state of the casting connection:

  • 如果狀態為 ConnectedRendering,則確保 ProgressRing 控制項為非使用中,並顯示中斷連線按鈕。If the state is Connected or Rendering, make sure the ProgressRing control is inactive and the disconnect button is visible.
  • 如果狀態為 Disconnected,則取消選取清單方塊中目前的傳播裝置、將 ProgressRing 控制項變成非使用中,並隱藏中斷連線按鈕。If the state is Disconnected, unselect the current casting device in the list box, make the ProgressRing control inactive, and hide the disconnect button.
  • 如果狀態為 Connecting,則將 ProgressRing 控制項變成使用中,並隱藏中斷連線按鈕。If the state is Connecting, make the ProgressRing control active and hide the disconnect button.
  • 如果狀態為 Disconnecting,則將 ProgressRing 控制項變成使用中,並隱藏中斷連線按鈕。If the state is Disconnecting, make the ProgressRing control active and hide the disconnect button.
private async void Connection_StateChanged(CastingConnection sender, object args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        //Update the UX based on the casting state
        if (sender.State == CastingConnectionState.Connected || sender.State == CastingConnectionState.Rendering)
        {
            disconnectButton.Visibility = Visibility.Visible;
            watcherProgressRing.IsActive = false;
        }
        else if (sender.State == CastingConnectionState.Disconnected)
        {
            disconnectButton.Visibility = Visibility.Collapsed;
            castingDevicesListBox.SelectedItem = null;
            watcherProgressRing.IsActive = false;
        }
        else if (sender.State == CastingConnectionState.Connecting)
        {
            disconnectButton.Visibility = Visibility.Collapsed;
            ShowMessageToUser("Connecting");
            watcherProgressRing.IsActive = true;
        }
        else
        {
            //Disconnecting is the remaining state
            disconnectButton.Visibility = Visibility.Collapsed;
            watcherProgressRing.IsActive = true;
        }
    });
}

ErrorOccurred 事件的處理常式中,更新 UI 讓使用者知道發生傳播錯誤,並取消選取清單方塊中目前的 CastingDevice 物件。In the handler for the ErrorOccurred event, update your UI to let the user know that a casting error occurred and unselect the current CastingDevice object in the list box.

private async void Connection_ErrorOccurred(CastingConnection sender, CastingConnectionErrorOccurredEventArgs args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        //Clear the selection in the listbox on an error
        ShowMessageToUser("Casting Error: " + args.Message);
        castingDevicesListBox.SelectedItem = null;
    });
}

最後,實作中斷連線按鈕的處理常式。Finally, implement the handler for the disconnect button. 呼叫 CastingConnection 物件的 DisconnectAsync 方法,停止媒體傳播並中斷連接傳播裝置。Stop media casting and disconnect from the casting device by calling the CastingConnection object's DisconnectAsync method. 必須呼叫 CoreDispatcher.RunAsync,將這個呼叫分派至 UI 執行緒。This call must be dispatched to the UI thread by calling CoreDispatcher.RunAsync.

private async void disconnectButton_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();
    }
}