Simple Któregoś dnia przeglądałem nowości w Silverlight Beta 5. Z wszystkich nowości zaciekawiła mnie możliwość zapętlania dźwięku. Jednak na razie można zapętlać tylko pliki .wav, ale może niedługo się to zmieni. Podczas przeglądania wpadłem na pomysł napisania prostego odtwarzacza muzycznego z możliwością zapętlania dźwięków w Silverlight 4.
Stworzenie aplikacji zajęło mi niecałą godzinę. Aplikacja bardzo mi się spodobała, może niedługo stworzę lepszą wersję i może nawet według wzoru MVVM. Aplikacja wygląda następująco.
A teraz czas na trochę informacji.
Parę informacji o kontrolce MediaElement
Kontrolka “MediaElement” potrafi odtworzyć większość formatów wideo oraz muzycznych. Gdy chcemy odtworzyć jakiś plik po prostu dopisujemy jego ścieżkę we właściwości “Source”. Gdy chcemy by utwór automatycznie się odtwarzał zaznaczamy to we właściwości “AutoPlay”.
Zarządzanie danym utworem za pomocą przycisków też jest banalnie proste. “Music” to nazwa kontrolki “MediaElement”.
private void Button_Click(object sender, RoutedEventArgs e)
{
Music.Stop();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Music.Pause();
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Music.Play();
}
Jeśli utwór został wcześniej zapauzowany przy odtworzeniu będzie grał dalej. Tyle, jeśli chodzi o kontrolkę MediaElement w tym wpisie blogowym.
Zapętlanie dźwięku w Silverlight
Silverlight o dziwo w wersji już 4 nie ma możliwości zapętlania utworów. Wydaje się to trochę dziwne, gdy pomyśleć, że we Flashu jest to norma. Aby zapętlić dźwięk z małą pauzą trzeba obsłużyć zdarzenie “MediaEnded” w następujący sposób.
private void MediaEnded(object sender, RoutedEventArgs e)
{
// Music.Stop();Music.Play();
}
Nie o to mi chodziło chciałem uzyskać zapętlenie dźwięku bez żadnych przerw, niczym utwory ze starych gier, które są tak naprawdę zdartą płytą jedno minutowego utworu. Starałem się uzyskać ten efekt w następujący sposób. Dla zdarzenia “MediaOpened”, które wykonuje się zawsze, gdy dany utwór zaczyna być odtwarzany dodałem następujący kod.
private void MediaOpened(object sender,
System.Windows.RoutedEventArgs e)
{
double time = Music.NaturalDuration.TimeSpan.TotalSeconds;
time = time - 0.2;
Music.Markers.Add(new TimelineMarker()
{ Time = TimeSpan.FromSeconds(time) });
Music.MarkerReached += new TimelineMarkerRoutedEventHandler (MarkerReached);
}
void MarkerReached(object sender,
TimelineMarkerRoutedEventArgs e)
{
Music.Position = TimeSpan.Zero;
}
Najpierw pobieram długość danego utworu i od tej długości odejmuję jedną piątą sekundy. Następnie dodaję do utworu marker (znacznik) w tym punkcie. Znaczniki bądź markery to fantastyczna sprawa, dzięki nim napisałem fajną animacje, która jest zsynchronizowana z danym utworem. Znaczniki pozwalają na wywołanie danego zdarzenia w określonym momencie utworu. W tym wypadu prawie pod koniec utworu utwór bez żadnego zatrzymywania zostanie cofnięty do początku.
Efekt nie jest taki zadowalający. Jeśli utwór nie został odpowiednio przygotowany i to jeszcze z małym poślizgiem, to o łagodnym przejściu można pomarzyć. Istnieje duża szansa usłyszenia trzasku po każdym skończonym odtworzeniu. Akurat w przypadku odtwarzacza muzycznego lepsze byłoby rozwiązanie prostsze.
Teraz na pewno rozumiecie, dlaczego fakt o możliwości zapętlania dźwięku w Silverlight 5 tak mnie ucieszył.
Pobieranie ścieżek za pomocą klasy WebClient
Normalnie, gdy podamy ścieżkę utworu w kontrolce MediaElement utwór będzie pobierany asynchronicznie i równocześnie odtwarzany. Nie jest to dziwne zachowanie, gdy popatrzymy na serwisy YouTube i tak dalej. Ja jednak postanowiłem pobrać najpierw cały strumień a później go odtworzyć. Efekt ten można uzyskać nie stosując wcale klasy WebClient . Kontrolka MediaElement ma właściwość “DownloadProgress” i zdarzenie “DownloadProgessChange”, ale klasa WebClient w tym scenariuszu, moim zdaniem ma większy sens.
Pliki muzyczne, jak i pliki graficzne zostały umieszczone w folderze “ClientBin” . Jeśli chodzi o ścieżki relatywne to Silverlight zaczyna od miejsca gdzie sam się znajduje. Zapomniałbym dodać, że wszystkie utwory skonwertowałem do formatu .mp4 przy użyciu “Expression Encoder-a” tak by zajmowały, jak najmniej miejsca.
Utwory są wybierane w “ComboBox-ie”. W zaznaczonym elemencie trzeba byłoby przekazać informacje takie jak, nazwa utworu muzycznego, nazwa ilustracji. Właśnie w tym momencie zdałem sobie sprawę, że warto by było zastosować tutaj jakiś prosty model, ale obeszło się bez tego. Nazwa ilustracji dla danego utworu jest taka sama jak jego zawartość “Content” a ścieżka jest przechowywana w elemencie “Tag”. Tak przy okazji ta właściwość jest fantastyczna, ponieważ można w niej umieścić cokolwiek do przechowywania.
<ComboBox x:Name="ComboBox_1" Width="155" >
<ComboBoxItem Content="Cannon Fodder - Phase Completed" Tag="Music/CannonFodderPhaseCompleted.mp4" />
</ComboBox>
Przy inicjalizacji tworzę tablicę strumieni, których ilość jest równa liczbie elementów w ComboBox-ie.
Stream[] Streams;
public MainPage()
{
InitializeComponent();
Streams = new Stream[ComboBox_1.Items.Count];
ComboBox_1.SelectionChanged +=new SelectionChangedEventHandler(ComboBox_SelectionChanged);
}
Gdy element został wybrany w combobox-ie zostają przechwycone jego informacje, jak index, tag i content. Sprawdzam czy dany strumień w tablicy jest pusty, jeśli jest to znaczy, że dany utwór nie został jeszcze pobrany więc go pobieram asynchronicznie za pomocą klasy WebClient. Ten kod na pewno nieraz widziałeś na moim blogu. Klasa WebClient posiada też zdarzenie “DownloadProgressChanged”, która w swoich argumentach ma właściwość “ProgressPercentage” stworzoną wręcz do kontrolki progressbar.
int index = ComboBox_1.SelectedIndex;
if (Streams[index] == null)
{
WebClient client = new WebClient();
client.OpenReadCompleted += (ss, ee) =>
{
if ((ee.Cancelled == false) && (ee.Error == null))
{
Streams[index] = ee.Result;
Music.SetSource(Streams[index]);
}
else {
MessageBox.Show("Error");
}
};
string url = (ComboBox_1.SelectedItem as ComboBoxItem).Tag.ToString();
client.OpenReadAsync(new Uri(url, UriKind.Relative));
client.DownloadProgressChanged += (ss, ee) =>
{
progressbar.Value = ee.ProgressPercentage;
};
}
else {
Music.SetSource(Streams[index]);
}
//Zmiana obrazka string
urliamge = (ComboBox_1.SelectedItem as ComboBoxItem).Content.ToString();
BitmapImage bmi = new BitmapImage(new Uri ("Picture/"+urliamge+".jpg", UriKind.Relative));
image.Source = bmi;
Jeśli chodzi o ilustracje nie widziałem sensu pobierania jej za pomocą klasy WebClient. W sumie tak samo jest ze ścieżką w klasie MediaElement obrazek pobiera się automatycznie z serwera. Warto jednak odnotować, że w XAML podajemy tylko ścieżkę a w kodzie we właściwości “Source” trzeba dodać klasę BitmapImage z tą właśnie ścieżką.
Zauważyłem, że nawet po restarcie aplikacji przeglądarka w tym wypadku “FireFox” zapamiętała pobrane strumienie. Pobrane strumienie mają status “304-Niezmodifikowany”. To taka ciekawostka, gdybym miał napisać możliwość zapamiętywania strumieni po wyjściu z aplikacji działającej poza przeglądarką, trzeba byłoby użyć “IsolatedStorage”.