MusicService invokes appropriate media player actions by calling media functions implemented in MusicPlayer. MusicPlayer will also contain a reference to MusicService so that it can notify MusicService if the media player's state changes.
Here are the steps to create MusicPlayer.
- Open Visual Studio 2017.
- Go to File | Open | Project/Solution, go to your MyPodCast directory, and choose the MyPodCast.sln file.
- Right-click on the MediaService folder | Add | New Item | select Visual C# Class and name the file MusicPlayer.cs.
- Click Add.
- Open MusicPlayer.cs by double-clicking the file.
- Create a public class, MusicPlayer, which implements AudioManager.IOnAudioFocusChangeListener, MediaPlayer.IOnCompletionListener, MediaPlayer.IOnErrorListener, MediaPlayer.IOnPreparedListener, and MediaPlayer.IOnSeekCompleteListener.
The following code shows the MusicPlayer class inheriting MediaPlayer interfaces:
public class MusicPlayer : Java.Lang.Object
, AudioManager.IOnAudioFocusChangeListener
, MediaPlayer.IOnCompletionListener
, MediaPlayer.IOnErrorListener, MediaPlayer.IOnPreparedListener
, MediaPlayer.IOnSeekCompleteListener
- Declare the following global variables:
-
- _musicService: Holds the MusicService object and sends various player states back to the MusicService
- _wifiLock: Used for accessing Wi-Fi during media streaming
- _musicProvider: Holds MusicProvider, which provides media metadata
- _mediaPlayer: MusicPlayer will utilize this MediaPlayer object for the play, pause, seek, and resume functions
- _playOnFocusGain: When set, MusicPlayer will play music; otherwise, the current media will be paused or the volume will be decreased or increased
- _currentPosition: When the media is paused, the play position will be saved
- _currentMediaId: MediaId of currently playing media
- _audioFocusState: State of the current audio
- _audioManager: AudioManager, controls the audio focus
- MusicPlayerState: Contains player states such as none, stopped, Paused, playing, fast forwarding, rewinding, buffering, error, connecting, skipping to previous, skipping to next, and skipping to queue item
- IsPlaying: Set to true if media is playing currently: return _playOnFocusGain ||(_mediaPlayer != null && _mediaPlayer.IsPlaying)
- CurrentStreamPosition: Position of currently playing media: return _mediaPlayer != null ? _mediaPlayer.CurrentPosition : _currentPosition
The following code shows the declared global variables for MusicPlayer:
private MusicService _musicService;
private WifiManager.WifiLock _wifiLock;
private MusicProvider _musicProvider;
private MediaPlayer _mediaPlayer;
private bool _playOnFocusGain;
private volatile int _currentPosition;
private volatile string _currentMediaId;
private AudioFocusState _audioFocusState;
private AudioManager _audioManager;
public PlaybackStateCode MusicPlayerState { get; set; }
public bool IsPlaying {
get {
return _playOnFocusGain ||(_mediaPlayer != null && _mediaPlayer.IsPlaying);
}
}
public int CurrentStreamPosition {
get {
return _mediaPlayer != null ? _mediaPlayer.CurrentPosition : _currentPosition;
}
}
- Create the MusicPlayer constructor, public MusicPlayer(MusicService service, MusicProvider musicProvider), which receives MusicService and MusicProvider. First, set MusicPlayerState to PlaybackStateCode.None if it is its first time being called and no media is selected to play. _audioFocusState is set to AudioFocusState.NoFocusAndNoHide, which means there is no focus on the audio and the audio is not in a hidden state. Set the _musicService and _musicProvider services, and musicProvider. Set _wifiLock to ((WifiManager) service.GetSystemService(Context.WifiService)).CreateWifiLock(WifiMode.Full, "mywifilock"). _wifiLock will obtain the current Wi-Fi settings from your phone, if any, and this is required for streaming media. Finally, create _mediaPlayer with SetWakeMode using _musicService.ApplicationContext and Android.OS.WakeLockFlags.Partial. SetWakeMode will allow your media player continue to utilize MusicService even after your phone locks. On _mediaPlayer set listeners to MusicPlayer: SetOnPreparedListener, SetOnCompletionListener, SetOnErrorListener and SetOnSeekCompleteListener.
The following code shows the MusicPlayer constructor:
public MusicPlayer(MusicService service, MusicProvider musicProvider)
{
MusicPlayerState = PlaybackStateCode.None;
_audioFocusState = AudioFocusState.NoFocusAndNoHide;
_musicService = service;
_musicProvider = musicProvider;
_audioManager = (AudioManager)service.GetSystemService(Context.AudioService);
_wifiLock = ((WifiManager)service.GetSystemService(Context.WifiService))
.CreateWifiLock(WifiMode.Full, "mywifilock");
if (_mediaPlayer == null)
{
_mediaPlayer = new MediaPlayer();
_mediaPlayer.SetWakeMode(_musicService.ApplicationContext,
Android.OS.WakeLockFlags.Partial);
_mediaPlayer.SetOnPreparedListener(this);
_mediaPlayer.SetOnCompletionListener(this);
_mediaPlayer.SetOnErrorListener(this);
_mediaPlayer.SetOnSeekCompleteListener(this);
}
}
- Implement IOnAudioFocusChangeListener, which is responsible for handling the lost focus on the current audio. Currently playing audio can lose a focus if another application takes control of the audio. For example, while listening to your podcast, Google Maps might be giving you directions, and you might want to either pause the podcast or lower the volume. When another application takes control, _audioFocusState can have two different states: AudioFocusState.NoFocusAndNoHide, which means to lose audio focus and pause the current media, and AudioFocusState.NoFocusAndCanHide, which means to lose audio focus and lower the volume. After setting proper _audioFocusState property call ConfigMediaPlayerState().
The following code shows OnAudioFocusChange:
public void OnAudioFocusChange(AudioFocus focusChange)
{
if (focusChange == AudioFocus.Gain)
_audioFocusState = AudioFocusState.Focused;
else if (focusChange == AudioFocus.Loss ||
focusChange == AudioFocus.LossTransient ||
focusChange == AudioFocus.LossTransientCanDuck)
{
bool canDuck = focusChange == AudioFocus.LossTransientCanDuck;
_audioFocusState = canDuck ? AudioFocusState.NoFocusAndCanHide : AudioFocusState.NoFocusAndNoHide;
_playOnFocusGain |= MusicPlayerState == PlaybackStateCode.Playing && !canDuck;
}
ConfigMediaPlayerState();
}
- Depends on _audioFocusState method ConfigMediaPlayerState() will play, pause, seek, or lower the volume of the media. After performing the action, it will notify MusicService of the changed state, _musicService.OnPlaybackStatusChanged(MusicPlayerState).
The following code shows ConfigMediaPlayerState:
private void ConfigMediaPlayerState()
{
if (_audioFocusState == AudioFocusState.NoFocusAndNoHide) {
if (MusicPlayerState == PlaybackStateCode.Playing)
Pause();
}
else {
if (_audioFocusState == AudioFocusState.NoFocusAndCanHide)
_mediaPlayer.SetVolume(0.2f, 0.2f);
else
_mediaPlayer.SetVolume(1.0f, 1.0f);
if (_playOnFocusGain) {
if (_mediaPlayer != null && !_mediaPlayer.IsPlaying) {
if (_currentPosition == _mediaPlayer.CurrentPosition) {
_mediaPlayer.Start();
MusicPlayerState = PlaybackStateCode.Playing;
}
else {
_mediaPlayer.SeekTo(_currentPosition);
MusicPlayerState = PlaybackStateCode.Buffering;
}
}
_playOnFocusGain = false;
}
}
if (_musicService != null)
_musicService.OnPlaybackStatusChanged(MusicPlayerState);
}
- Implement IOnCompletionListener.OnCompletion, which gets called when the podcast finishes playing. It will notify MusicService that the media has finished playing.
The following code shows IOnCompletionListener.OnCompletion:
public void OnCompletion(MediaPlayer mp)
{
if(_musicService != null)
_musicService.OnCompletion();
}
- Implement IOnErrorListener.OnError, which will send an error message to MusicService.
The following code shows IOnErrorListener.OnError:
public bool OnError(MediaPlayer mp, MediaError what, int extra)
{
if (_musicService != null)
_musicService.OnError("MediaPlayer error " + what + "(" + extra + ")");
return true;
}
- Implement IOnPreparedListener.OnPrepared, which is called when the media is being prepared in order to be played. Any special logic that needs to be done before playing the media can be handled here.
The following code shows IOnPreparedListener.OnPrepared:
public void OnPrepared(MediaPlayer mp)
{
ConfigMediaPlayerState();
}
- Implement IOnSeekCompleteListener.OnSeekComplete, which notifies MusicService when the media player has found its position in the media.
The following code shows IOnSeekCompleteListener.OnSeekComplete:
public void OnSeekComplete(MediaPlayer mp)
{
_currentPosition = mp.CurrentPosition;
if (MusicPlayerState == PlaybackStateCode.Buffering) {
_mediaPlayer.Start();
MusicPlayerState = PlaybackStateCode.Playing;
}
if (_musicService != null)
_musicService.OnPlaybackStatusChanged(MusicPlayerState);
}
- Create the Stop method, which will be called by MusicService. When the media stops MusicPlayerState will be set to PlaybackStateCodeStoped and then _currentPosition is set to CurrentStreamPosition in case the media needs to resume.
The following code shows the Stop method:
public void Stop(bool notifyListeners)
{
MusicPlayerState = PlaybackStateCode.Stopped;
if (notifyListeners && _musicService != null)
_musicService.OnPlaybackStatusChanged(MusicPlayerState);
_currentPosition = CurrentStreamPosition;
GiveUpAudioFocus();
CleanUp(true);
}
- Create the Pause method. When the media is paused using the function _mediaPlayer.Pause(), _currentPosition is set to _mediaPlayer.CurrentPosition in order to resume from the pasued position. It will notify MusicService of the paused state.
The following code shows the Paused method:
public void Pause()
{
if (MusicPlayerState == PlaybackStateCode.Playing) {
if (_mediaPlayer != null && _mediaPlayer.IsPlaying) {
_mediaPlayer.Pause();
_currentPosition = _mediaPlayer.CurrentPosition;
}
CleanUp(false);
GiveUpAudioFocus();
}
MusicPlayerState = PlaybackStateCode.Paused;
if (_musicService != null)
_musicService.OnPlaybackStatusChanged(MusicPlayerState);
}
- Create the SeekTo method, which will get invoked when the player resumes or fast forwards to a certain position in the media. In order to resume playing, the player must buffer to the new position by invoking _mediaPlayer.SeekTo(position).
The following code shows the SeekTo method:
public void SeekTo(int position)
{
if (_mediaPlayer == null)
_currentPosition = position;
else
{
if (_mediaPlayer.IsPlaying)
MusicPlayerState = PlaybackStateCode.Buffering;
_mediaPlayer.SeekTo(position);
if (_musicService != null)
_musicService.OnPlaybackStatusChanged(MusicPlayerState);
}
}
- Create the public void Play(MediaSession.QueueItem item) method, which will get invoked when the user hits the play button. item contains MediaId, which can be used to get MediaMetadata from _musicProvider.GetMusic. MediaMetadata contains the media streaming source, PodcastSource. The following methods will be invoked in order to initialize the media player:
- _mediaPlayer.Reset: Resets the media player since you will be playing a new podcast
- MusicPlayerState: Set this to PlaybackStateCode.Buffering and the player will buffer the steam for few seconds
- _mediaPlayer.SetAudioStreamType: Set it to Android.Media.Stream.Music
- _mediaPlayer.SetDataSource: Set it to the podcast source URL
- _mediaPlayer.PrepareAsync: Invoke to prepare the media, which will trigger IOnPreparedListener.OnPrepared when finished preparing the media
- _wifiLock.Acquire: A Wi-Fi lock needs to be acquired so that the player can stream data using Wi-Fi
- _musicService.OnPlaybackStatusChanged: Notify the MusicService of the status of the player
The following code shows the Play method:
public void Play(MediaSession.QueueItem item) {
var mediaHasChanged = InitPlayerStates(item.Description.MediaId);
if (MusicPlayerState == PlaybackStateCode.Paused && !mediaHasChanged && _mediaPlayer != null)
ConfigMediaPlayerState();
else {
MusicPlayerState = PlaybackStateCode.Stopped;
CleanUp(false);
MediaMetadata track = _musicProvider.GetMusic(
HierarchyHelper.ExtractMusicIDFromMediaID(item.Description.MediaId));
string source = track.GetString(MusicProvider.PodcastSource);
try {
_mediaPlayer.Reset();
MusicPlayerState = PlaybackStateCode.Buffering;
_mediaPlayer.SetAudioStreamType(Android.Media.Stream.Music);
_mediaPlayer.SetDataSource(source);
_mediaPlayer.PrepareAsync();
_wifiLock.Acquire();
_musicService.OnPlaybackStatusChanged(MusicPlayerState);
}
catch (Exception ex) {
_musicService.OnError(ex.Message);
}
}
}