Nell'articolo precedente abbiamo visto come implementare il contratto Play To in un'applicazione Windows Store, in modo da permettere all'applicazione (detta anche "source") di effettuare streaming di file multimediali verso un device (TV, Xbox, ecc.) certificato Play To. In questo articolo vedremo come creare un'applicazione in grado di ricevere questo stesso streaming (in questo caso si parla di applicazioni "receiver" o "target").
Per prima cosa, è necessario modificare il file Package.appxmanifest in modo da consentire all'applicazione receiver di fungere da server all'interno della rete privata. Per far questo, è necessario dichiarare nell'application manifest la capability "Private Networks (Client & Server)", come mostrato nella prossima immagine.
Adesso che abbiamo aggiunto la dichiarazione all'application manifest, possiamo cominciare a implementare l'applicazione target. Per prima cosa, aggiungiamo alcuni controlli XAML alla pagina in modo da poter visualizare lo streaming proveniente dall'applicazione source. Puoi usare la seguente definizione XAML come riferimento per la pagina principale della tua app (MainPage.xaml).
<Page
x:Class="Demo.Html.it.PlayToReceiverSample.CS.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Demo.Html.it.PlayToReceiverSample.CS"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Button x:Name="btnStopReceiving"
Click="StopReceivingButton_Click"
Content="Interrompi ricezione" />
<MediaElement x:Name="VideoPlayerReceiver" Width="680" Height="480" AutoPlay="True" />
<TextBlock x:Name="tbMessage" Text="L'applicazione è pronta a ricevere..." FontSize="18" />
</StackPanel>
</Grid>
</Page>
Per implementare il contratto Play To dal lato receiver sono necessari una serie di passaggi:
- In primo luogo, occorre istanziare un nuovo oggetto di tipo
PlayToReceiver
, ossia la classe responsabile per la gestione della comunicazione tra l'applicazione target ed eventuali client Play To; - quindi assegnare un nome all'applicazione receiver che identifichi l'app sulla rete locale;
- abbonarsi agli eventi esposti dalla classe
PlayToReceiver
- definire quali tipologie di file multimediali la nostra app è in grado di supportare;
- infine, invocare il metodo
StartAsync
per mettersi in ascolto sulla rete.
Il metodo StartReceivingButton_Click
mostra i passaggi necessari.
private PlayToReceiver _receiver;
private async void StartReceivingButton_Click(object sender, RoutedEventArgs e)
{
this._receiver = new PlayToReceiver();
this._receiver.FriendlyName = "MyPlayToReceiver";
this._receiver.CurrentTimeChangeRequested += receiver_CurrentTimeChangeRequested;
this._receiver.MuteChangeRequested += receiver_MuteChangeRequested;
this._receiver.PauseRequested += receiver_PauseRequested;
this._receiver.PlaybackRateChangeRequested += receiver_PlaybackRateChangeRequested;
this._receiver.PlayRequested += receiver_PlayRequested;
this._receiver.SourceChangeRequested += receiver_SourceChangeRequested;
this._receiver.StopRequested += receiver_StopRequested;
this._receiver.TimeUpdateRequested += receiver_TimeUpdateRequested;
this._receiver.VolumeChangeRequested += receiver_VolumeChangeRequested;
this._receiver.SupportsVideo = true;
this._receiver.SupportsAudio = false;
this._receiver.SupportsImage = false;
await this._receiver.StartAsync();
this.MessageTextBlock.Text = "L'applicazione è adesso pronta a ricevere come " + this._receiver.FriendlyName;
}
Esaminiamo ora il codice più nel dettaglio.
Dopo aver creato una nuova istanza della classe PlayToReceiver
, il codice imposta la proprietà FriendlyName
. Si tratta di un passaggio necessario (la sua omissione comporta una InvalidOperationException
a runtime), poiché questo sarà il nome che identificherà l'applicazione receiver sulla rete privata, come mostrato nella prossima immagine.
Dopo che la classe PlayToReceiver
è stata istanziata e il friendly name
assegnato, è necessario sottoscrivere i vari eventi esposti dalla classe PlayToReceiver
. Questi eventi sono sollevati dal sistema in risposta ad azioni compiute dall'utente sull'applicazione client. Gli eventi sono i seguenti:
Evento | Descrizione |
---|---|
CurrentTimeChangeRequested | La posizione temporale della riproduzione è cambiata |
MuteChangeRequested | L'audio è stato (dis)attivato |
PauseRequested | La riproduzione è stata messa in pausa |
PlaybackRateChangeRequested | La velocità di riproduzione è cambiata |
PlayRequested | Il playback è stato avviato |
SourceChangeRequested | L'elemento multimediale è cambiato |
StopRequested | L'esecuzione è stata interrotta |
TimeUpdateRequested | La posizione della riproduzione corrente è cambiata |
VolumeChangeRequested | Il volume è cambiato |
È fondamentale ricordare che occorre sottoscrivere tutti gli eventi sopra menzionati. Se dimentichiamo di abbonarci anche a un solo evento, nel momento in cui il metodo StartAsync
viene invocato per iniziare a ricevere lo streaming, il sistema solleverà una InvalidOperationException
con il messaggio "A Method Was Called At An Unexpected Time" (la stessa eccezione sollevata se dimentichiamo di impostare la proprietà FriendlyName
).
Per questo è importante ricordarsi di sottoscrivere tutti gli eventi esposti dalla classe PlayToReceiver
, anche se alcuni di questi hanno un senso solo se riferiti a specifici tipologie di elementi multimediali, come video e musica.
Questo comportamento, apparentemente curioso, è in realtà motivato dal fatto che, implementando il contratto Play To, l'applicazione target deve poter essere in grado di rispondere alle differenti azioni che l'utente può compiere sull'applicazione source, come ad esempio avviare, interrompere e mettere in pausa la riproduzione del file multimediale.
Ricordiamo inoltre che, una volta sottoscritto un certo evento, si può sempre decidere di non implementare il relativo handler, se questo tipo di evento non ha senso per il tipo di file multimediali che l'app è in grado di riprodurre (ad esempio, implementare l'handler dell'evento MuteChangeRequested
in un'app dedicata allo streaming di foto potrebbe non avere molto senso).
Per filtrare il tipo di elementi multimediali che l'app è in grado di supportare, possiamo sfruttare le tre proprietà di tipo Boolean
esposte sempre dalla classe PlayToReceiver
: SupportsAudio
, SupportsVideo
, e SupportsImage
. Queste proprietà, dai nomi piuttosto auto-esplicativi, indicano al sistema quale tipo di elemento multimediale la tua app è effettivamente in grado di riprodurre.
this._receiver.SupportsVideo = true;
this._receiver.SupportsAudio = false;
this._receiver.SupportsImage = false;
Infine, il metodo asincrono StartAsync
della classe PlayToReceiver
mette l'app in ascolto di richieste da parte di eventuali client Play To e "proietta" il receiver sulla rete private come digital media renderer pronto a ricevere lo streaming proveniente da applicazioni source:
await this._receiver.StartAsync();
Analogamente, quando vuoi interrompere la ricezione dello streaming da un'applicazione source, puoi utilizzare il metodo asincrono StopAsync
e rimuovere gli hander dei vari eventi, come mostrato nel prossimo snippet:
private async void StopReceivingButton_Click(object sender, RoutedEventArgs e)
{
if (this._receiver != null)
{
await this._receiver.StopAsync();
this._receiver.CurrentTimeChangeRequested -= receiver_CurrentTimeChangeRequested;
this._receiver.MuteChangeRequested -= receiver_MuteChangeRequested;
this._receiver.PauseRequested -= receiver_PauseRequested;
this._receiver.PlaybackRateChangeRequested -= receiver_PlaybackRateChangeRequested;
this._receiver.PlayRequested -= receiver_PlayRequested;
this._receiver.SourceChangeRequested -= receiver_SourceChangeRequested;
this._receiver.StopRequested -= receiver_StopRequested;
this._receiver.TimeUpdateRequested -= receiver_TimeUpdateRequested;
this._receiver.VolumeChangeRequested -= receiver_VolumeChangeRequested;
this.MessageTextBlock.Text = "L'applicazione non è più in grado di ricevere...";
}
}
Il passo successivo consiste nell'implementare gli handler degli eventi esposti dalla classe PlayToReceiver
, in modo da poter reagire alle azioni compiute sull'applicazione source. Dal momento che la comunicazione tra le due applicazioni è bidirezionale, è anche possibile notificare al receiver l'esito delle operazioni compiute sull'elemento multimediale tramite uno dei vari metodi Notify
* esposti sempre dalla classe PlayToReceiver
, come mostrato nel prossimo snippet:
private async void receiver_VolumeChangeRequested(PlayToReceiver sender, VolumeChangeRequestedEventArgs args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.VideoPlayerReceiver.Volume = args.Volume;
this._receiver.NotifyVolumeChange(this.VideoPlayerReceiver.Volume,
this.VideoPlayerReceiver.IsMuted);
});
}
private async void receiver_TimeUpdateRequested(PlayToReceiver sender, object args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
if (this.VideoPlayerReceiver.Position != null)
{
this._receiver.NotifyTimeUpdate(this.VideoPlayerReceiver.Position);
}
});
}
private async void receiver_StopRequested(PlayToReceiver sender, object args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.VideoPlayerReceiver.Stop();
this._receiver.NotifyStopped();
});
}
private async void receiver_SourceChangeRequested(PlayToReceiver sender, SourceChangeRequestedEventArgs args)
{
if (args.Stream != null)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.VideoPlayerReceiver.SetSource(args.Stream, args.Stream.ContentType);
this.VideoPlayerReceiver.Play();
});
}
}
private async void receiver_PlayRequested(PlayToReceiver sender, object args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.VideoPlayerReceiver.Play();
this._receiver.NotifyPlaying();
});
}
private async void receiver_PlaybackRateChangeRequested(PlayToReceiver sender, PlaybackRateChangeRequestedEventArgs args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.VideoPlayerReceiver.PlaybackRate = args.Rate;
});
}
private async void receiver_PauseRequested(PlayToReceiver sender, object args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.VideoPlayerReceiver.Pause();
this._receiver.NotifyPaused();
});
}
private async void receiver_MuteChangeRequested(PlayToReceiver sender, MuteChangeRequestedEventArgs args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.VideoPlayerReceiver.IsMuted = args.Mute;
this._receiver.NotifyVolumeChange(VideoPlayerReceiver.Volume, true);
});
}
private async void receiver_CurrentTimeChangeRequested(PlayToReceiver sender, CurrentTimeChangeRequestedEventArgs args)
{
if (this.VideoPlayerReceiver.CanSeek)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.VideoPlayerReceiver.Position = args.Time;
this._receiver.NotifySeeking();
});
}
}
Lo stesso pattern può essere usato per notificare all'applicazione client che qualcosa è andato storto nel receiver. Ad esempio, il prossimo snippet mostra l'handler dell'evento MediaFailed
esposto dalla classe MediaElement
per notificare che si è verificato un errore durante la riproduzione di un element multimediale (ad esempio, il formato non è stato riconosciuto o non è supportato).
this.VideoPlayerReceiver.MediaFailed += VideoPlayerReceiver_MediaFailed;
...
private void VideoPlayerReceiver_MediaFailed(object sender, ExceptionRoutedEventArgs e)
{
if (this._receiver != null)
{
this._receiver.NotifyError();
}
}