⬆️ ⬇️

We write a simple player for Windows Phone

This article demonstrates how to write the simplest music player under Windows Phone.



Key features: the player works with a playlist, you can control the music (switch melodies), the player can play in the background.



The author was primarily guided by an article in which some elementary, but not always obvious, aspects of player writing were not covered. They are dismantled in this article.



')

Creating a project







We take as a basis the usual Silverlight for Windows Phone solution, let's call it SimplePlayer .







To the existing solution we will add another project of the Windows Phone Audio Playback Agent type , let's call it AudioPlaybackAgent .







In the project SimplePlayer add a link to the AudioPlaybackAgent .





SimplePlayer is the main project, it contains and maintains the graphical interface and is launched first, and AudioPlaybackAgent is a shell over the internal Windows Phone player that runs from the main project when a track is requested to play.

The communication between the two projects, during the execution of the application, is quite limited and imposes some restrictions, we will consider how to overcome them.



Interface design



We will deal with the design: it is quite simple and immediately makes it clear what functionality the application should have.

To do this, open MainPage.xaml , which contains the markup interface.

1. It is necessary to replace the StackPanel named TitlePanel with this code:

<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="SIMPLE PLAYER" Style="{StaticResource PhoneTextNormalStyle}"/> <TextBlock x:Name="PageTitle" Text="playlist" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel> 


2. It is necessary to replace the Grid named ContentPanel with the code:

 <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel Orientation="Vertical"> <ListBox x:Name="PlayListBox" SelectionChanged="PlayListBox_SelectionChanged"/> <StackPanel Orientation="Horizontal"> <Button x:Name="PrevButton" Content="prev" Height="140" Width="140" Click="PrevButton_Click"/> <Button x:Name="PlayButton" Content="play" Height="140" Width="140" Click="PlayButton_Click"/> <Button x:Name="NextButton" Content="next" Height="140" Width="140" Click="NextButton_Click"/> </StackPanel> </StackPanel> </Grid> 


After that, the interface in the designer will look like this:





With this we are done with the design.



Playlist



It's time to talk about how we will store the playlist with tracks and where we will take these tracks.



Add some mp3 or wma tracks to the SipmlePlayer project. (In this example: 1.mp3, 2.mp3, 3.mp3)







Now open the MainPage.xaml.cs file, which presents the code that serves our interface. Add a field to the MainPage class:

 private List<string> playlist; 


The same field should be added to the AudioPlaybackAgent project in the AudioPlayer class.



In this field we will store our list of files, in favor of simplicity we immediately refuse to store and display the name of the tracks, artists and albums.



We will not store the playlist statically in the code, but in the XML file that will be deserialized in the playlist field. Add an xml file to the main project, let's name it playlist.xml . Content will be:

 <?xml version="1.0" encoding="utf-8"?> <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <string>1.mp3</string> <string>2.mp3</string> <string>3.mp3</string> </ArrayOfString> 


For another list of files you just need to add, change, delete tags.







Attention: Do not forget to set the Build Action in the Content and Copy to Output Directory files to Copy always in all music and playlist files.



It's time to make playlist.xml available for both projects. To do this, copy it to IsolatedStorage , which both projects have access to. The repository, at the moment, is the only way to communicate information to the background agent in addition to events and the current track.



The function of saving the file from the project to the repository:

 private void CopyToIsolatedStorage(string fullFilePath, string storeFileName) { using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) { if (!storage.FileExists(storeFileName)) { StreamResourceInfo resource = Application.GetResourceStream(new Uri(fullFilePath, UriKind.Relative)); using (IsolatedStorageFileStream file = storage.CreateFile(storeFileName)) { const int chunkSize = 4096; byte[] bytes = new byte[chunkSize]; int byteCount; while ((byteCount = resource.Stream.Read(bytes, 0, chunkSize)) > 0) { file.Write(bytes, 0, byteCount); } } } } } 


It needs to be added to the MainPage class, it does not make sense to look at it in detail in this article. Then we call it from the constructor:

 CopyToIsolatedStorage("playlist.xml", "playlist.xml"); 


And now we will deserialize it and for this we will write a function, which also needs to be called in the constructor immediately after the previous one.

 private void LoadPlaylist() { using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication()) { using (IsolatedStorageFileStream stream = myIsolatedStorage.OpenFile("playlist.xml", FileMode.Open)) { XmlSerializer serializer = new XmlSerializer(typeof(List<string>)); playlist = (List<string>)serializer.Deserialize(stream); } } } 


The same function should be copied into the AudioPlaybackAgent project and add its call in the constructor of the AudioPlayer class.





Attention: For this function, you must add a link to System.Xml.Serialization .



We will now take care of loading music files by writing a function to be called in the constructor MainPage class immediately after LoadPlaylist () .

 private void LoadMusicFiles() { foreach (var filepath in playlist) { CopyToIsolatedStorage(filepath, filepath); } } 


Warning: This method is not optimal and critical delays the loading of the application, I advise you to resort to BackgroundWorker for real applications.



So that our playlist is displayed and the user can work with it, in the MainPage class in the constructor, after calling InitializeComponent (), we add the line:

 PlayListBox.ItemsSource = playlist; 




Event handlers



When we edited the XAML code, we declared event handlers, but did not describe them. Consider the code:

 private void PlayListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { string filepath = (string)e.AddedItems[0]; BackgroundAudioPlayer.Instance.Track = new AudioTrack(new Uri(filepath,UriKind.Relative),null,null,null,null); BackgroundAudioPlayer.Instance.Play(); } private void PrevButton_Click(object sender, RoutedEventArgs e) { BackgroundAudioPlayer.Instance.SkipPrevious(); } private void PlayButton_Click(object sender, RoutedEventArgs e) { if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing) BackgroundAudioPlayer.Instance.Pause(); else BackgroundAudioPlayer.Instance.Play(); } private void NextButton_Click(object sender, RoutedEventArgs e) { BackgroundAudioPlayer.Instance.SkipNext(); } 


The question may arise, what is the BackgroundAudioPlayer ?



This, in fact, is the only direct connection with the background agent.

In these handlers, we set the behavior of our agent, but we can also receive events from it (for example, stop the track, start playing, etc.), for this we add a method to the MainPage class:

 void Instance_PlayStateChanged(object sender, EventArgs e) { switch (BackgroundAudioPlayer.Instance.PlayerState) { case PlayState.Playing: PlayButton.Content = "pause"; //  ,    PlayListBox.SelectedItem = BackgroundAudioPlayer.Instance.Track.Source.OriginalString; break; case PlayState.Paused: case PlayState.Stopped: PlayButton.Content = "play"; break; } } 




And in the constructor of the MainPage class, we associate it with our agent with the string:

 BackgroundAudioPlayer.Instance.PlayStateChanged += Instance_PlayStateChanged; 


It is worth noting that the BackgroundAudioPlayer.Instance.PlayerState field contains the current background agent state, i.e. music plays, paused, over, and so on. There is also the BackgroundAudioPlayer.Instance.Position field, which reflects the current place to play in the file, which can also be useful, we omit this feature.



At this stage, our project is almost finished, but switching tracks does not work, and there is no transition to the next track if the current one has finished playing.



In the AuidioPlayer class, you can see two interesting functions OnPlayStateChanged and OnUserAction . The first is responsible for handling changes in the state of the agent, in the second - for processing the impact of the main project. In both methods, stub methods are invoked, which serve up the next and previous track.



We realize:

 private AudioTrack GetNextTrack() { int next = (playlist.IndexOf(BackgroundAudioPlayer.Instance.Track.Source.OriginalString) + 1) % (playlist.Count); return new AudioTrack(new Uri(playlist[next], UriKind.Relative), null, null, null, null); ; } private AudioTrack GetPreviousTrack() { int prev = (playlist.IndexOf(BackgroundAudioPlayer.Instance.Track.Source.OriginalString) -1 + playlist.Count) % (playlist.Count); return new AudioTrack(new Uri(playlist[prev], UriKind.Relative), null, null, null, null); ; } 


For reasons unknown to the author, the template developers decided that at the end of the track ( PlayState.TrackEnded ), the previous track will be played next, to solve this misunderstanding, you need to replace player.Track = GetPreviousTrack () in the OnPlayStateChanged handler ; on player.Track = GetNextTrack (); .



If the user has not selected a track, and he presses one of the buttons (play / next / prev), then the current track is also not selected in the AuidioPlayer class, and therefore critical exceptions may occur.

At the first user event, we define the current track as the first one, if it is not already selected, adding the first line to the OnUserAction method:

 if (player.Track == null) player.Track = new AudioTrack(new Uri(playlist[0], UriKind.Relative), null, null, null, null); 




Now our application has all the declared basic functionality that is required for each player.



Conclusion



Implementing a player is a fairly typical task, and I hope that the article made it clear how to make a simple player.



Source code: download / view

Sample article based application: New Year's Music

Source: https://habr.com/ru/post/139113/



All Articles