Una delle API più originali esposte da WinRT è rappresentata dalla funzionalità denominata Play To, attivabile tramite il charm Devices quando l'app che implementa il relativo contratto si trova in primo piano.
Questa feature permette di effettuare lo stream di elementi multimediali, come video, musica e foto, da un'applicazione Windows Store verso altri device connessi alla rete (come TV, Xbox e altri ricevitori abilitati).
Tutte le applicazioni native di Windows 8 dedicate al multimedia, come Music, Videos, Photos e lo stesso Internet Explorer 10, implementano il contratto Play To. E' dunque possibile guardare un video sul proprio PC o tablet e, con un semplice gesto, condividerlo con i nostri amici e familiari sulla TV di casa o su uno qualunque dei device certificati Windows 8. Le periferiche che sfruttano questa funzionalità espongono un apposito logo, mostrato nella prossima immagine (l'elenco aggiornato dei produttori e dei device che supportano questa funzionalità può essere verificato qui).
In questo articolo, vedremo come implementare un'applicazione Windows Store in XAML/C# in grado di effettuare lo streaming di elementi multimediali verso un qualunque device compatibile con questa tecnologia. Nel prossimo articolo, invece, vedremo come realizzare un'applicazione Windows Store in grado di ricevere questo stesso streaming implementando il contratto Play To Receiver.
Il contratto Play To
Perché la funzionalità Play To possa funzionare, è necessario essere connessi a una rete e aver abilitato la condivisione delle risorse, come mostrato nella prossima immagine
Non appena abilitata la condivisione, Windows aggiungerà automaticamente tutti i device connessi alla rete. E' possibile controllare in qualsiasi momento l'elenco dei dispositivi presenti tramite il pannello PC Settings Devices, mostrato nella prossima immagine
Se non ci sono device Play To connesse alla rete, o se è stata disabilitata l'opzione di condivisione, cliccando sul charm Devices otterremo il seguente messaggio.
Nota: Windows 8.1 ha modificato leggermente il comportamento del charm Devices. Per attivare la funzione Play To, dopo aver attivato il charm, occorre selezionare Play, come mostrato nella seguente immagine
È importante sottolineare che qualunque applicazione Windows Store che "lavori" con elementi multimediali è già in grado di sfruttare per default la funzionalità Play To. Infatti, se l'utente seleziona il charm Devices quando l'app è in esecuzione e clicca su uno dei device in grado di funzionare come ricevitore Play To, Windows effettuerà lo stream utilizzando il primo elemento multimediale presente nella pagina.
A questo punto potremmo domandarci perché implementare il contratto di Play To se la nostra app è già in grado di sfruttarne le relative funzionalità. La risposta è che il comportamento di default può andare bene per applicazioni che presentano un solo elemento multimediale per pagina, mentre se vogliamo creare applicazioni dotate di un'interazione con l'utente anche minimamente evoluta, come la possibilità di creare playlist di video e musica, o di effettuare uno slide show delle foto presenti nella libreria dell'utente, è necessario implementare il contratto Play To.
Questo contratto introduce un elevato livello di astrazione rispetto alle sottostanti tecnologie e protocolli di streaming, e rispetto ai vari formati multimediali, rendendo l'implementazione di questa funzionalità all'interno della tua applicazione Windows Store relativamente semplice.
Il seguente snippet mostra il pattern di base che caratterizza il contratto PlayTo
(avremo modo di approfondire i vari dettagli nelle prossime pagine):
private PlayToManager _ptm;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this._ptm = PlayToManager.GetForCurrentView();
this._ptm.SourceRequested += ptm_SourceRequested;
}
private void ptm_SourceRequested(Windows.Media.PlayTo.PlayToManager sender, PlayToSourceRequestedEventArgs args)
{
try
{
args.SourceRequest.SetSource(mediaElement.PlayToSource);
}
catch (Exception ex)
{
// notificare all'utente
}
}
Per prima cosa, il codice ottiene una reference a un'istanza della classe Windows.Media.PlayTo.PlayToManager
(ossia la classe responsabile della gestione delle varie operazioni di streaming) specifica per la pagina corrente invocando il metodo statico PlayToManager.GetForCurrentView
, quindi sottoscrive l'evento SourceRequested
esposto sempre dalla classe PlayToManager
.
Questo evento viene scatenato dal sistema quando l'utente attiva il charm Devices, ed è qui che occorre impostare l'elemento multimediale da usare per lo stream tramite il metodo SetSource
dell'oggetto di tipo PlayToSourceRequestedEventArgs
ricevuto come parametro. A questo punto, quando, l'utente seleziona uno dei device Play To elencati nel pannello Devices, l'applicazione inizierà lo streaming del dell'elemento multimediale passato al metodo SetSource
verso il device selezionato.
Lo streaming verso il device target continuerà anche se l'applicazione è stata messa in background perché l'utente ha nel frattempo lanciato un'altra app. Questo perché l'applicazione source che utilizza l'API Play To non viene sospesa dal sistema fino a quando il video o il brano musicale continuano ad essere eseguiti sul device selezionato come target, ovvero fino a quando nuove immagini vengono inviate al ricevitore. In altri termini, il sistema mantiene l'app in esecuzione fino a quando la sessione Play To è ancora attiva. In particolare, secondo la documentazione ufficiale MSDN, un'applicazione ha circa 10 secondi per inviare un nuovo file audio o video dopo che la riproduzione del contenuto precedente è terminata, ovvero per inviare una nuova immagine dopo che quella precedente è stata visualizzata, prima che questa sia posta in sospensione dal sistema.
Creare un ambiente di test tramite il Windows Media Player
Prima di implementare la nostra applicazione source, vediamo come impostare l'ambiente di test in modo da poter verificare il corretto funzionamento dell'applicazione anche se non si dispone di un ricevitore compatibile con la funzionalità Play To.
Per testare questa funzionalità, è infatti possibile sfruttare Windows Media Player in esecuzione su un altro computer connesso in rete (PC, notebook, tablet o anche una macchina virtuale con sopra Windows 8). E' sufficiente lanciare un'istanza di Windows Media Player sul dispositivo remoto e, nel menu Stream, abilitare l'opzione Allow Remote Control Of My Player, come mostrato nella prossima immagine (è importante non chiudere l'istanza di Windows Media Player fino a quando non abbiamo terminato di testare la nostra applicazione).
A questo punto torna sulla macchina in locale e, nel pannello PC Settings, clicca su Devices. Windows dovrebbe riconoscere e installare il nuovo dispositivo target non appena disponibile. Se l'istanza remota di Windows Media Player non viene visualizzata nell'elenco, clicca sul pulsante Add Device nell'angolo superiore destro del pannello. La prossima immagine mostra un'istanza di Windows Media Player in esecuzione su una macchina virtuale con sopra Windows 8.
A questo punto possiamo utilizzare la funzione Play To per effettuare lo streaming di un elemento multimediale verso la macchina remota. Dallo Start screen di Windows 8, lanciamo una delle applicazioni native di Windows 8, come Photos o Videos, selezioniamoun elemento multimediale dalla libreria, attiviamo il charm Devices; quindi selezioniamo l'istanza di Windows Media Player in esecuzione sul device remoto.
La prossima immagine mostra lo streaming di un video dalla macchina locale verso l'istanza di Windows Media Player in esecuzione su una macchina virtuale.
Implementare un'applicazione Play To source
Per prima cosa, aggiungiamo alcuni controlli XAML alla pagina principale (MainPage.xaml
) in modo da visualizzare il video su schermo. Possiamo usare il seguente codice come riferimento per l'applicazione.
<Page
x:Class="Demo.Html.it.PlayToSource.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.PlayToSource.CS"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<Button x:Name="StartPlaying" Click="StartVideoButton_Click" Content="Avvia Video" Margin="20" />
<Button x:Name="ButtonToggleStreaming" Click="ButtonToggleStreaming_Click" Content="Gestisci streaming" Margin="20" />
<MediaElement x:Name="VideoPlayer" Width="640" Height="480" Margin="20" />
<TextBlock x:Name="MessageTextBlock" Width="400" Height="Auto" FontSize="16" Margin="20" />
</StackPanel>
</Page>
In questa semplice applicazione demo ci limiteremo a effettuare lo stream del primo video disponibile nella libreria Videos dell'utente (accertati di avere almeno un video nella tua libreria se vuoi testare il codice proposto in questo articolo). Dal momento che il codice ha bisogno di accedere alle librerie dell'utente, è necessario dichiarare la corrispondente "capability" nell'application manifest dell'applicazione, altrimenti il sistema solleverà una eccezione di tipo UnauthorizedAccessException
. La prossima immagine mostra il file Package.appxmanifest con la relativa dichiarazione.
Il seguente snippet di codice mostra l'handler dell'evento di click che carica il primo video trovato nella libreria Videos dell'utente sfruttando il metodo GetFilesAsync
della classe VideosLibrary
e quindi lo esegue in loop impostando a true
la proprietà IsLooping
dell'oggetto MediaElement
.
private async void StartVideoButton_Click(object sender, RoutedEventArgs e)
{
try
{
var resultsLibrary = await Windows.Storage.KnownFolders.VideosLibrary.GetFilesAsync();
if (resultsLibrary != null && resultsLibrary.Count > 0)
{
MessageTextBlock.Text += "Video in riproduzione: " + resultsLibrary[0].Name + "\n";
IRandomAccessStream videoStream = await resultsLibrary[0].OpenAsync(FileAccessMode.Read);
String mimeType = resultsLibrary[0].FileType;
VideoPlayer.SetSource(videoStream, mimeType);
VideoPlayer.IsLooping = true;
VideoPlayer.Play();
}
else
MessageTextBlock.Text = "Nessun video da riprodurre";
}
catch (Exception ex)
{
MessageTextBlock.Text = "Qualcosa è andato storto: " + ex.Message;
}
}
Nel metodo OnNavigatedTo
recuperiamo una reference all'oggetto PlayToManager
per la pagina corrente tramite il metodo statico GetForCurrentView
, quindi ci abboniamo a entrambi gli eventi esposti dalla classe, ossia SourceRequested
e SourceSelected
(quest'ultimo evento verrà descritto in dettaglio più avanti):
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this._ptm = Windows.Media.PlayTo.PlayToManager.GetForCurrentView();
this._ptm.SourceRequested += ptm_SourceRequested;
this._ptm.SourceSelected += ptm_SourceSelected;
}
Come abbiamo già accennato, l'evento SourceRequested viene sollevato dal sistema quando l'utente attiva il charm Devices. Nel relativo handler, il codice utilizza la proprietà SourceRequest
dell'oggetto PlayToSourceRequestedEventArgs
ricevuto come parametro per impostare l'elemento multimediale (nella forma di un oggetto di tipo PlayToSource
) da inviare al device target. La proprietà SourceRequest
è un'istanza della classe PlayToSourceRequest
e, come il nome suggerisce, rappresenta una richiesta di streaming dell'elemento multimediale verso il device Play To. Il seguente codice mostra questo passaggio.
private async void ptm_SourceRequested(PlayToManager sender, PlayToSourceRequestedEventArgs args)
{
var deferral = args.SourceRequest.GetDeferral();
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
var controller = VideoPlayer.PlayToSource;
controller.Connection.Error += PlayTo_ConnectionError;
controller.Connection.StateChanged += PlayTo_ConnectionStageChanged;
controller.Connection.Transferred += PlayTo_ConnectionTransferred;
args.SourceRequest.SetSource(controller);
deferral.Complete();
});
}
Questo codice mostra anche altri punti che vale la pena di approfondire. In primo luogo, WinRT dà all'applicazione 200 millisecondi per impostare l'elemento multimediale da inviare in stream, trascorsi i quali l'evento SourceRequested
andrà in time-out e il charm Devices non mostrerà alcun target Play To cui inviare lo stream. Nel caso in cui si renda necessario un intervallo di tempo maggiore, le API di WinRT mettono a disposizione il metodo GetDeferral
della classe PlayToSourceRequest
per creare un deferral ed effettuare una chiamata asincrona per recuperare il file multimediale.
A questo punto WinRT attenderà fino a quando il deferral non sarà marcato come completato mediante l'invocazione del metodo Complete
(l'intervallo di tempo concesso da WinRT per la conclusione dell'operazione asincrona non risulta ancora documentato ufficialmente su MSDN; tuttavia, è possibile determinare il tempo rimanente ispezionando la proprietà Deadline
, di tipo DateTimeOffset
, esposta dalla classe PlayToSourceRequest
).
L'oggetto di tipo PlayToSource
passato al metodo SetSource
rappresenta come si è detto l'elemento multimediale da inviare in streaming al device target. Questa classe espone una proprietà Connection
(di tipo PlayToConnection
) che, tramite tre eventi specifici, permette di essere aggiornati sullo stato della connessione con il target Play To. Gli eventi sono i seguenti:
Evento | Descrizione |
---|---|
Error | Scatenato al verificarsi di un errore nella connessione (ad esempio, il device target non risponde, è bloccato o è in errore); il tipo di errore può essere ispezionato mediante la proprietà Code (di tipo PlayToConnectionError ) dell'oggetto PlayToConnectionErrorEventArgs ricevuto come parametro dal relativo handler. Questa proprietà può assumere i valori di DeviceError , DeviceNotResponding , DeviceLocked o, nel caso si stia cercando di inviare file protetti, ProtectedPlaybackFailed . |
StateChanged | Scatenato quando lo stato della connessione cambia e indica, tramite l'oggetto di tipo PlayToConnectionStateChangedEventArgs ricevuto come parametro dal relativo handler, lo stato corrente (rappresentato dalla proprietà CurrentState ) e lo stato precedente al cambiamento (rappresentato dalla proprietà PreviousState ). Entrambe queste due proprietà sono di tipo PlayToConnectionState , un enum che può assumere uno dei seguenti valori: Disconnected , Connected e Rendering . |
Transferred | Scatenato ogniqualvolta un nuovo file multimediale viene inviato in streaming al device target. Le due proprietà CurrentSource e PreviousSource , esposte dall'oggetto di tipo PlayToConnectionTransferredEventArgs ricevuto come parametro dal relativo handler consentono di conoscere, rispettivamente, l'elemento sorgente corrente e quello precedente. |
Il seguente listato mostra gli handler per i tre eventi sopra esposti.
private async void PlayTo_ConnectionError(PlayToConnection connection, PlayToConnectionErrorEventArgs e)
{
if (e.Code == PlayToConnectionError.DeviceError ||
e.Code == PlayToConnectionError.DeviceNotResponding)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
this.MessageTextBlock.Text += "Si è verificato un errore. Disconnessione in corso....\n";
});
}
}
private async void PlayTo_ConnectionStageChanged(PlayToConnection connection, PlayToConnectionStateChangedEventArgs e)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
this.MessageTextBlock.Text += "Stato precedente = " + e.PreviousState.ToString() + "\n";
this.MessageTextBlock.Text += "Stato corrente = " + e.CurrentState.ToString() + "\n";
});
}
private async void PlayTo_ConnectionTransferred(PlayToConnection connection, PlayToConnectionTransferredEventArgs e)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
this.MessageTextBlock.Text += "Connessione trasferita: PreviousSource = " + e.PreviousSource.ToString() + "\n";
this.MessageTextBlock.Text += "Connessione trasferita: CurrentSource = " + e.CurrentSource.ToString() + "\n";
});
}
La prossima immagine mostra una porzione della finestra applicativa con i vari stati della connessione durante lo streaming.
Come abbiamo già accennato, oltre all'evento SourceRequested
, la classe PlayToManager
espone anche un secondo evento, ossia SourceSelected. Questo evento viene sollevato quando l'utente seleziona uno specifico device Play To dall'elenco di quelli disponibili. In altri termini, se l'utente clicca sul charm Devices (facendo così scattare l'evento SourceRequested
), ma poi non seleziona alcun target Play To dall'elenco, l'evento SourceSelected
non viene sollevato. Il relativo handler rappresenta il luogo ideale per controllare che il target Play To selezionato supporti effettivamente lo specifico tipo di file multimediale da inviare. Il seguente snippet mostra un esempio di utilizzo di questo evento:
private void ptm_SourceSelected(PlayToManager sender, PlayToSourceSelectedEventArgs args)
{
if (!args.SupportsVideo)
{
this.MessageTextBlock.Text = args.FriendlyName + " non supporta lo streaming video. Per favore seleziona un altro dispositivo";
return;
}
}
Come vedremo meglio nell'articolo dedicato alle applicazioni Play To target, la proprietà FriendlyName
dell'oggetto PlayToSourceSelectedEventArgs
ricevuto come parametro dal relativo handler identifica il device Play To target sulla rete locale.
Infine un'ultima considerazione. Per avviare lo streaming dell'elemento multimediale (così come per interromperlo) è necessario utilizzare il pannello attivabile tramite il charm Devices. Sebbene non sia possibile iniziare lo streaming o disconnettere il device target via codice, le API di WinRT mettono comunque a disposizione una "scorciatoia" rappresentata dal metodo statico ShowPlayToUI
esposto dalla classe PlayToManager
, il quale semplicemente mostra il pannello Play To senza che sia necessario cliccare sul relativo charm. Il seguente metodo ne mostra il funzionamento.
private void ButtonToggleStreaming_Click(object sender, RoutedEventArgs e)
{
Windows.Media.PlayTo.PlayToManager.ShowPlayToUI();
}