Media w Windows Phone 7 - cz. I  

Udostępnij na: Facebook

Autor: Marcin Kruszyński

Opublikowano: 2011-05-11

Pobierz i uruchom

Twórcy aplikacji multimedialnych na Windows Phone 7 mają wiele możliwości. W swoich aplikacjach mogą odtwarzać audio i wideo na kilka różnych sposobów, uzyskiwać dostęp do lokalnych zasobów multimedialnych na telefonie, korzystać z radia, nagrywać z użyciem mikrofonu, integrować się z hubem Music+Videos.

Po przeczytaniu tego artykułu będziesz:

  • znał różne sposoby na odtwarzanie audio i wideo,
  • wiedział, jakie są ich właściwości i ograniczenia, 
  • umiał dostać się do muzyki przechowywanej na telefonie.

W artykule Media w Windows Phone 7 - cz. II znajdziesz informacje na temat korzystania z radia i mikrofonu oraz dowiesz się, jak możesz zintegrować swoją aplikację z hubem Music+Videos.

Wprowadzenie

Windows Phone 7 wspiera szeroki zbiór kodeków audio i wideo (szczegóły znajdziesz w dokumentacji na stronie Supported Media Codecs for Windows Phone). Platforma pozwala nam na realizację adaptatywnego streamingu. Mamy także obsługę zabezpieczeń przed nieuprawnionym odtwarzaniem w postaci technologii PlayReady Digital Right Management (DRM).      

Obsługa multimediów w Silverlight na Windows Phone 7 bazuje na natywnym stosie. W stosunku do wersji desktopowej mamy jednak pewne ograniczenia, m.in. brak wsparcia dla VideoBrush, markerów, playlist (po stronie klienta i serwera). Pełny opis różnic znajdziesz w dokumentacji na stronie Media in Silverlight for Windows Phone.

W aplikacji możemy odtwarzać multimedia za pomocą:

  • MediaElement – kontrolka Silverlight, która obsługuje pliki audio i wideo,  
  • MediaPlayerLauncher – odtwarza pliki audio i wideo za pomoca systemowej aplikacji odtwarzacza multimedialnego,
  • Efektów dźwiękowych – API w XNA, co umożliwia modyfikację dźwięku,
  • MediaPlayer – API w XNA, zapewnia funkcjonalności znane z odtwarzacza systemowego.

Wszystkie te sposoby omówimy dokładnie w dalszych częściach tego artykułu.

MediaElement

Kontrolka MediaElement dostarcza prostokątny region, który potrafi wyświetlić wideo na swojej powierzchni lub odtwarza sam dźwięk w przypadku plików audio.

Odtwarzanie lokalnego pliku możemy przykładowo zrealizować w następujący sposób:

<MediaElement x:Name="mediaElement1" Source="movie.wmv" AutoPlay="True" Volume="0.9"/>

Powinniśmy unikać umieszczania pliku multimedialnego w assembly w postaci resource, ponieważ duży rozmiar assembly ma negatywny wpływ na wydajność. Plik możemy zawrzeć w archiwum xap naszej aplikacji. Należy wtedy w projekcie ustawić dla niego opcje:

  • BuildAction na wartość Content,
  • Copy to Output Directory na wartość Copy if newer lub Copy always.

Alternatywnie możemy podać adres zdalny. Wtedy plik będzie strumieniowany lub pobrany i odtworzony, jak w przeglądarce.

Przy domyślnej wartości true parametru AutoPlay zawartość multimedialna jest automatycznie odtwarzana po ustawieniu parametru Source. Odtwarzaniem możemy również sterować sami za pomocą metod Play, Pause i Stop.   

Kontrolka MediaElement w Windows Phone 7 nie wspiera niektórych funkcjonalności znanych z wersji desktopowej, m.in wielu instancji (jednocześnie może być aktywny jeden MediaElement), markerów, trybu pełnoekranowego (ale możemy ustawić jej rozmiar na rozmiar aplikacji), umieszczania swojej zawartości w screenshotach WriteableBitmap.

Podczas odtwarzania MediaElement zatrzymuje wszystkie inne źródła audio odtwarzane w tle na telefonie. Dlatego nie powinniśmy używać tej kontrolki dla efektów dźwiękowych w aplikacji, lepiej użyć do tego celu SoundEffect API z XNA. Kontrolka MediaElement jest przydatna do odtwarzania pełnoekranowego wideo lub w innych sytuacjach, gdzie dźwięk w tle może być zatrzymany.

MediaPlayerLauncher

Alternatywnym sposobem na odtwarzanie plików wideo i audio jest skorzystanie z taska MediaPlayerLauncher, który uruchamia aplikację Media Player i odtwarza wskazany plik (rys. 1).

Rys. 1 Odtwarzanie wideo za pomocą MediaPlayerLauncher

Przykładowy kod może wyglądać następująco:

 

var mediaPlayerLauncher = new MediaPlayerLauncher
            {
                Location = MediaLocationType.Install,
                Media = new Uri("movie.wmv", UriKind.Relative),
                Controls = MediaPlaybackControls.Pause | MediaPlaybackControls.Stop
            };
mediaPlayerLauncher.Show();

Pliki mogą być umieszczone w Isolated Storage lub w katalogu instalacyjnym aplikacji (gdy są zawarte w jej archiwum .xap). Miejsce musimy określić za pomocą typu wyliczeniowego MediaLocationType. Opcjonalnie możemy też podać zbiór kontrolek wyświetlanych w Media Player, korzystając z wartości typu MediaPlaybackControls lub ich kombinacji.

Gdy będziemy używać MediaPlayerLauncher w emulatorze, możemy spotkać się z sytuacją, że ramki wideo będą renderowane jedynie przy ciągłym klikaniu myszą (po kliknięciu nastąpi wyrenderowanie jednej ramki i pojawi się czarny ekran).

SoundEffect API

Dźwięk możemy odtwarzać i modyfikować za pomocą klas XNA: SoundEffect, SoundEffectInstance i DynamicSoundEffectInstance. SoundEffect API – w przeciwieństwie do MediaElement – nie blokuje odtwarzania w tle innych dźwięków (w Windows Phone 7 możemy jednocześnie odtwarzać je do 64).

Przy korzystaniu z API XNA w Silverlight powinniśmy emulować pętlę gry, okresowo wywołując metodę FrameworkDispatcher.Update. Do tego celu możemy użyć DispatcherTimer, umieszczając go bezpośrednio na stronie lub w serwisie aplikacji (którego przykładem jest XNAFrameworkDispatcher z Windows Phone 7 Developer Guide, opisany w Appendix B - Silverlight and XNA in Windows Phone 7). 

Aby odtworzyć dźwięk za pomocą klasy SoundEffect, wystarczy stworzyć jej instancję z danymi w formacie PCM wav i wywołać na niej metodę Play. Plik z zasobów aplikacji możemy odtworzyć w następujący sposób:

var soundFileInfo = App.GetResourceStream(new Uri("sound.wav", UriKind.Relative));
var sound = SoundEffect.FromStream(soundFileInfo.Stream);
sound.Play();

W wywołaniu metody Play możemy przekazać parametry dźwięku (głośność, pitch, balans), ale nie możemy ich zmieniać po rozpoczęciu odtwarzania. SoundEffect nie pozwala także na sterowanie odtwarzaniem.

Jeśli jednak zechcemy sterować odtwarzaniem (wstrzymywać, wznawiać, zapętlać) i zmieniać parametry dźwięku (głośność, pitch, balans, położenie w przestrzeni) w dowolnym momencie odtwarzania, potrzebujemy instancji klasy SoundEffectInstance. Tworzymy ją poprzez wywołanie metody CreateInstance na obiekcie klasy SoundEffect:

varsoundInstance = sound.CreateInstance();

Klasa SoundEffectInstance, oprócz metody Play, udostępnia także metody Stop, Pause i Resume oraz propercję State (typu wyliczeniowego SoundState o wartościach Playing, Paused i Stopped). Powinniśmy sprawdzać jej wartość przed próbą odtworzenia, zatrzymania, wstrzymania lub wznowienia odtwarzania pliku dźwiękowego. A jej ciągły monitoring (np. z użyciem DispatchTimer) może być przydatny przy wykrywaniu zakończenia odtwarzania (np. w celu aktualizacji interfejsu użytkownika). 

Jeśli chcemy zapętlić odtwarzanie, to przed wywołaniem metody Play ustawiamy:

soundInstance.IsLooped = true;

W każdym momencie odtwarzania możemy modyfikować takie parametry dźwięku jak głośność, balans, tempo.

soundInstance.Volume = 0.8f
;soundInstance.Pan = 0.5f; 
soundInstance.Pitch = -0.5f;

Możemy także generować dźwięk 3D. Dźwięk umieszczamy w przestrzeni poprzez stworzenie obiektów klas AudioEmitter i AudioListener oraz wywołanie metody Apply3D na obiekcie klasy SoundEffectInstance.

AudioEmitter emitter = newAudioEmitter();
AudioListener listener = newAudioListener();
soundInstance.Apply3D(listener, emitter);
soundInstance.Play();

W trakcie odtwarzania możemy zmieniać położenie dźwięku poprzez zmianę propercji Position na obiekcie klasy AudioEmitter lub AudioListener. Aby uzyskać wrażenie przemieszczania się po okręgu, wystarczy, aby timer wywoływał co sekundę poniższy kod (globalna zmienna secondsElapsed przechowuje czas w sekundach od momentu włączenia timera):

secondsElapsed++;
  var objectPos = newVector3((float)Math.Cos(secondsElapsed) / 2, 0,
                 (float)Math.Sin(secondsElapsed));
  emitter.Position = objectPos;
  soundInstance.Apply3D(listener, emitter);

Bezpośredni dostęp do bufora audio zyskujemy za pomocą klasy DynamicSoundEffectInstance. Możemy dynamicznie modyfikować lub generować dźwięk, dzielić duże pliki na mniejsze części i strumieniować. Praktyczny przykład na generowanie dźwięku w kształcie sinusoidy o zadanej częstotliwości możesz znaleźć w artykule  Using DynamicSoundEffectInstance to Create Sounds at Runtime on Windows Phone 7. O strumieniowaniu pliku wav możesz poczytać  w dokumentacji na stronie Streaming Data from a WAV File.  

MediaPlayer

Statyczna klasa MediaPlayer z XNA pozwala na odtwarzanie dźwięku w tle. Nie zostanie ono przerwane, nawet jeśli program zostanie zamknięty lub ekran telefonu wyłączony lub zablokowany. Jest to zachowanie charakterystyczne dla standardowego odtwarzacza w telefonie.

Klasa MediaPlayer odtwarza pojedynczy obiekt klasy Song albo kolekcję SongCollection (wszystkie piosenki lub od danej pozycji). Nie można sami utworzyć kolekcji SongCollection, pozyskujemy je z klas reprezentujących muzykę znajdującą się lokalnie na telefonie. Instancję klasy Song możemy utworzyć na podstawie Uri (metoda Song.FromUri) lub pozyskać ją z SongCollection.

Dostęp do muzyki na telefonie uzyskujemy za pomocą klasy MediaLibrary (reprezentuje lokalną multimedialną bazę Zune, dodatkowo zapewnia dostęp do zdjęć, o czym możesz przeczytać w artykule Zdjęcia w Windows Phone 7). Mamy do dyspozycji cztery propercje:

  • Albums, typu AlbumCollection, kolekcji obiektów klasy Album,
  • Songs, typu SongCollection, kolekcji obiektów klasy Song,
  • Artists, typu ArtistCollection, kolekcji obiektów klasy Artist,
  • Genres, typu GenreCollection, kolekcji obiektów klasy Genre.

Każda z tych kolekcji zawiera całą muzykę, ale uporządkowaną w inny sposób.

Klasa Album zawiera m.in. nazwę, wykonawcę, kolekcję piosenek, informację o udostępnionej okładce (odpowiednio propercje – Name, Artist, Songs, HasArt). Jeśli HasArt ma wartość true, to możemy wywołać na obiekcie albumu metody GetAlbumArt i GetThumbnail zwracające strumienie z okładką i jej miniaturą.

W klasie Song zawarte są informacje o nazwie utworu, albumie, wykonawcy, czasie trwania (propercje: Name, Album, Artist, Duration).

Założmy więc, że chcemy wyświetlić listę wykonawców z MediaLibrary, a dla każdego z nich listę jego albumów z miniaturami okładek. W takiej sytuacji może być przydatny następujący kod:

MediaLibrary mediaLib = new MediaLibrary();
var albumsByArtist = new Dictionary<string, List<AlbumInfo>>();

foreach (var artist in mediaLib.Artists)
{
    albumsByArtist.Add(artist.Name, artist.Albums.Select(album => {
              var albumInfo = new AlbumInfo { Album = album };

              if (album.HasArt)
              {
                  var bitmapImage = new BitmapImage();
                  bitmapImage.SetSource(album.GetThumbnail());
                  albumInfo.ThumbnailArt = bitmapImage;
              }

              return albumInfo;
            }).ToList());
}

mediaLib.Dispose();

Zastosowana przez nas pomocnicza klasa AlbumInfo przechowuje miniaturę okładki w postaci bitmapy, którą można bindować do kontrolki Image w interfejsie użytkownika.

Omówimy teraz samą klasę MediaPlayer. Najważniejsze jej propercje to:

  • State – typ wyliczeniowy MediaState (o wartościach Playing, Paused, Stopped),
  • PlayPosition – typ TimeSpan, pozycja aktualnie odtwarzanego utworu,
  • Queue –  typ MediaQueue (kolejka obiektów Song, aktualnie odtwarzany utwór – propercja ActiveSong),
  • Volume – poziom głośności.

Bardzo przydatne okazują się też metody sterujące odtwarzaniem Play, Pause, Resume, Stop, a także MovePrevious i MoveNext, powodujące przejście do poprzedniego lub następnego utworu w kolekcji SongCollection. MediaPlayer definiuje także dwa zdarzenia MediaStateChanged i ActiveSongChanged, które mogą przydać się np. do aktualizacji interfejsu użytkownika.

Pokażemy poniżej, jak można zorganizować odtwarzanie listy utworów z wybranego albumu. Załóżmy, że chcemy oprogramować trzy przyciski: start/pause/resume, prev i next. Możemy zrobić to w następujący sposób (zmienna album reprezentuje interesujący nas album):

void OnPlayButtonClick(object sender, EventArgs args)
{
    switch (MediaPlayer.State)
    {                
        case MediaState.Playing:
            MediaPlayer.Pause();
            break;
               
        case MediaState.Paused:
            MediaQueue queue = MediaPlayer.Queue;
                   
            if (queue.ActiveSong != null &&
                        queue.ActiveSong.Album == album)
            {
                MediaPlayer.Resume();
            }                   
            else
            {
                goto case MediaState.Stopped;
            }
            break;
                
        case MediaState.Stopped:
            MediaPlayer.Play(album.Songs);
            break;
    }
}
void OnPreviousButtonClick(object sender, EventArgs args)
{
     MediaPlayer.MovePrevious();
}

void OnNextButtonClick(object sender, EventArgs args)
{
     MediaPlayer.MoveNext();
}

W naszym rozwiązaniu nie wywołujemy metody Stop przy opuszczeniu strony lub aplikacji, ponieważ chcemy mieć takie samo zachowanie jak w systemowym odtwarzaczu. Muzyka nie przestanie grać po wyjściu z naszej aplikacji.

Załóżmy, że chcemy wyświetlać użytkownikowi napis z zegarem zmieniającym się w trakcie odtwarzania utworu w zestawieniu z jego długością. Przydatne okazać się może okresowe wywoływanie w timerze następującego kodu (zmienna song oznacza odtwarzany utwór):

TimeSpan dur = song.Duration;
TimeSpan pos = MediaPlayer.PlayPosition;

var text = String.Format("{0}:{1:D2} / {2}:{3:D2}", (int)pos.TotalMinutes,
             pos.Seconds,(int)dur.TotalMinutes, dur.Seconds);

Podsumowanie

W tym artykule poznaliśmy różne sposoby na odtwarzanie audio i wideo, ich właściwości i ograniczenia. Wiemy już, za pomocą jakich klas można modyfikować i generować dźwięk. Potrafimy przeglądać i odtwarzać piosenki z albumów znajdujących się na telefonie użytkownika.

W kolejnym artykule nauczymy się korzystać z radia i mikrofonu oraz integrować nasze aplikacje z hubem Music+Videos.