Nell'articolo precedente abbiamo visto come sfruttare la funzionalità Play To introdotta da Windows 8, che permette ad un'applicazione Windows Store (detta anche "source") di effettuare lo streaming di file multimediali verso un device (TV, Xbox, ecc.) certificato. In questo articolo vedremo come implementare il contratto Play To in un'applicazione Windows Store in modo da ricevere questo stesso streaming (in questo caso si parla di applicazioni "receiver" o "target").
Dal momento che l'applicazione receiver ha bisogno di mettersi in ascolto sulla rete privata in attesa dello streaming proveniente da applicazioni client, è necessario innanzitutto modificare il file Package.appxmanifest
per aggiungere la capability "Private Networks (Client & Server
)", come mostrato nella prossima immagine.
Una volta aggiunta la dichiarazione all'application manifest, iniziamo a implementare l'applicazione target. Utilizziamo il seguente markup HTML come riferimento per la pagina principale dell'app (default.html
).
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Demo.Html.it.PlayToReceiverSample.JS</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.2.0/js/base.js"></script>
<script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
<!-- Demo.Html.it.PlayToReceiverSample.JS references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body>
<table>
<tr>
<td>
<button id="btnStartReceiving">Avvia ricezione</button>
</td>
<td style="text-align: right">
<button id="btnStopReceiving">Interrompi la ricezione</button>
</td>
</tr>
<tr>
<td colspan="2">
<video id="videoPlayer" width="800" height="600" />
</td>
</tr>
<tr>
<td colspan="2">
<div id="message" />
</td>
</tr>
</table>
</body>
</html>
Perché la nostra applicazione possa gestire la comunicazione con eventuali client Play To, sono necessari una serie di passaggi:
- in primo luogo, occorre istanziare un nuovo oggetto di tipo
PlayToReceiver
, che rappresenta 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 serva a identificarla sulla rete privata; abbonarsi ai vari eventi esposti dalla classe
PlayToReceiver
; - dichiarare quali tipologie di file multimediali la nostra app è in grado di supportare;
- quando tutto è pronto, invocare il metodo
StartAsync
per mettersi in ascolto sulla rete.
L'handler startReceiverButton_click
mostra i passaggi necessari.
app.onloaded = function (args) {
btnStartReceiving.addEventListener("click", startReceiverButton_click);
btnStopReceiving.addEventListener("click", stopReceiverButton_click);
}
var receiver;
var display;
function startReceiverButton_click() {
try {
if (receiver == null) {
receiver = new Windows.Media.PlayTo.PlayToReceiver();
receiver.friendlyName = "MyPlayToReceiver";
receiver.addEventListener("currenttimechangerequested", receiver_CurrentTimeChangeRequested);
receiver.addEventListener("mutechangerequested", receiver_MuteChangeRequested);
receiver.addEventListener("pauserequested", receiver_PauseRequested);
receiver.addEventListener("playbackratechangerequested", receiver_PlaybackRateChangeRequested);
receiver.addEventListener("playrequested", receiver_PlayRequested);
receiver.addEventListener("sourcechangerequested", receiver_SourceChangeRequested);
receiver.addEventListener("stoprequested", receiver_StopRequested);
receiver.addEventListener("timeupdaterequested", receiver_TimeUpdateRequested);
receiver.addEventListener("volumechangerequested", receiver_VolumeChangeRequested);
receiver.supportsVideo = true;
receiver.supportsAudio = false;
receiver.supportsImage = false;
receiver.startAsync()
.done(function () {
if (display == null) {
display = new Windows.System.Display.DisplayRequest();
}
display.requestActive();
message.innerHTML = "'" + receiver.friendlyName + "' started.";
});
}
}
catch (e) {
receiver = null;
message.innerHTML = "Qualcosa è andato storto durante l’inizializzazione.";
}
}
Vediamo adesso il codice più nel dettaglio.
Dopo aver istanziato un nuovo oggetto di tipo PlayToReceiver
, il codice imposta la proprietà FriendlyName
. Si tratta di un passaggio necessario (la sua omissione comporta il sollevamento di un'eccezione 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à un'eccezione accompagnata dal messaggio "A Method Was Called At An Unexpected Time
" (la stessa eccezione sollevata nel caso in cui tu abbia dimenticato di assegnare un friendly name al receiver).
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.
Bisogna inoltre ricordare che una volta sottoscritto un certo evento, possiamo sempre decidere di non implementare il relativo handler, se questo tipo di evento non ha senso per il tipo di file multimediali che la nostra 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.
receiver.supportsVideo = true;
receiver.supportsAudio = false;
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:
receiver.startAsync()
.done(function () {
if (display == null) {
display = new Windows.System.Display.DisplayRequest();
}
display.requestActive();
message.innerHTML = "'" + receiver.friendlyName + "' started.";
});
Analogamente, quando vogliamo interrompere la ricezione dello streaming da un'applicazione source, possiamo utilizzare il metodo asincrono StopAsync e rimuovere gli hander dei vari eventi, come mostrato nel prossimo snippet:
function stopReceiverButton_click(e) {
try {
if (receiver != null) {
receiver.stopAsync().done(function () {
receiver.removeEventListener("currenttimechangerequested", receiver_CurrentTimeChangeRequested);
receiver.removeEventListener("mutechangerequested", receiver_MuteChangeRequested);
receiver.removeEventListener("pauserequested", receiver_PauseRequested);
receiver.removeEventListener("playbackratechangerequested", receiver_PlaybackRateChangeRequested);
receiver.removeEventListener("playrequested", receiver_PlayRequested);
receiver.removeEventListener("sourcechangerequested", receiver_SourceChangeRequested);
receiver.removeEventListener("stoprequested", receiver_StopRequested);
receiver.removeEventListener("timeupdaterequested", receiver_TimeUpdateRequested);
receiver.removeEventListener("volumechangerequested", receiver_VolumeChangeRequested);
message.innerHTML = "L'applicazione non è più in grado di ricevere...";
});
}
}
catch (e) {
message.innerHTML = "Qualcosa è andato storto: " + e.message;
}
}
Il prossimo passo 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, tramite il receiver, l'esito delle operazioni compiute sull'elemento multimediale mediante uno dei vari metodi Notify
* esposti sempre dalla classe PlayToReceiver
, come mostrato nel prossimo snippet.
function receiver_CurrentTimeChangeRequested(args) {
if (videoPlayer.currentTime !== 0 || args.time !== 0) {
videoPlayer.currentTime = args.time / 1000;
receiver.notifySeeking();
}
}
function receiver_MuteChangeRequested(args) {
videoPlayer.muted = args.mute;
receiver.notifyVolumeChange(videoPlayer.volume, videoPlayer.muted);
}
function receiver_PauseRequested() {
videoPlayer.pause();
receiver.notifyPaused();
}
function receiver_PlaybackRateChangeRequested(args) {
videoPlayer.playbackRate = args.rate;
receiver.notifyRateChange(args.rate);
}
function receiver_PlayRequested() {
videoPlayer.play();
}
function receiver_SourceChangeRequested(args) {
if (args.stream != null) {
var mediaStream = MSApp.createBlobFromRandomAccessStream(
args.stream.contentType, args.stream);
videoPlayer.src = URL.createObjectURL(mediaStream, false);
}
}
function receiver_StopRequested() {
if (videoPlayer.readyState != 0) {
videoPlayer.pause();
videoPlayer.currentTime = 0;
receiver.notifyStopped();
}
}
function receiver_TimeUpdateRequested() {
receiver.notifyTimeUpdate(videoPlayer.currentTime * 1000);
}
function receiver_VolumeChangeRequested(args) {
videoPlayer.volume = args.volume;
receiver.notifyVolumeChange(videoPlayer.volume, videoPlayer.muted);
}
Lo stesso pattern può essere usato per notificare all'applicazione client che qualcosa è andato storto nel receiver. Ad esempio, il prossimo snippet mostra un esempio di gestione di un eventuale errore durante la riproduzione di un elemento multimediale (ad esempio, perché il formato non è stato riconosciuto o non è supportato).
videoPlayer.addEventListener("error", videoPlayer_Error);
...
function videoPlayer_Error() {
receiver.notifyError();
receiver.notifyStopped();
}