You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on May 6, 2024. It is now read-only.
For about a year, I have been working on a podcast app myself. Implementing playback support for Android was something I was hesitating to do, and then luckily for me dotnet-podcasts were released that helped me a lot with this. Thank you for this project!
However, I have found a few issues, and solutions (at least I think so, I haven’t I run dotnet-podcasts myself). I’m too lazy to make a PR, so instead I share these here.
Stopping notifications
If the application for some reason crashes, in many cases the player notification remains open. To solve this (or at least reduce the risk for this), I modified MainApplication and added several calls to StopNotification like this:
The pause button in the notification is visible when the audio is playing. But it should also be visible when the state is buffering or stopped. In MediaPlayerService.UpdateNotification, replace:
MediaPlayerState==PlaybackStateCode.Playing
With:
MediaPlayerStateisPlaybackStateCode.Playing or PlaybackStateCode.Buffering or PlaybackStateCode.Stopped
Position and Duration properties in MediaPlayerService
The properties Position and Duration in MediaPlayerService have valid values depending on state. I modified these to provide a cached value if the underlying media player cannot provide this:
privateint_LatestValidPosition=-1;publicintPosition{get{varpos=RawPosition;if(pos>=0){_LatestValidPosition=pos;}return_LatestValidPosition;}privateset{_LatestValidPosition=value;}}privateintRawPosition{get{if(mediaPlayerisnull||!(MediaPlayerStateisPlaybackStateCode.Playing or PlaybackStateCode.Paused or PlaybackStateCode.Buffering)){return-1;}else{returnmediaPlayer.CurrentPosition;}}}privateint_LatestValidDuration=-1;publicintDuration{get{varduration=RawDuration;if(duration>0){_LatestValidDuration=duration;}return_LatestValidDuration;}set{_LatestValidDuration=value;}}privateintRawDuration{get{if(mediaPlayerisnull||!(MediaPlayerStateisPlaybackStateCode.Playing or PlaybackStateCode.Paused or PlaybackStateCode.Buffering){return0;}else{returnmediaPlayer.Duration;}}}
_LatestValidDuration and _LatestValidPosition should be set to -1 in PrepareAndPlayMediaPlayerAsync.
MediaPlayerService.Seek
The seek method I have modified to this:
publicasyncTaskSeek(intposition){Logger.LogInformation($"{nameof(Seek)} - position {position}");UpdatePlaybackState(PlaybackStateCode.Buffering);awaitTask.Run(()=>{if(mediaPlayeris not null){Position=position;mediaPlayer.SeekTo(position);}});}
MediaPlayerService.PrepareAndPlayMediaPlayerAsync
In PrepareAndPlayMediaPlayerAsync I have added:
mediaPlayer.Pause();mediaPlayer.Reset();
before the call to SetDataSourceAsync. If I remember correctly this solved a couple of cases where an IllegalStateException was thrown. But I have done other changes too, not sure if this is needed.
MediaPlayerService.OnAudioFocusChange
The MediaPlayerService automatically starts media player when it gains audio focus. This is great – if the service also has automatically paused the audio. Currently, if the audio is manually paused by the user and then the user gets a phone call (or a notification), the audio will be restarted after the phone call is completed. It is very surprising :-). I rewrote the method like this:
privateboolRestartAudioOnGainAudioFocus=false;publicasyncvoidOnAudioFocusChange(AudioFocusfocusChange){Logger.LogInformation($"{nameof(OnAudioFocusChange)} - {focusChange}");switch(focusChange){caseAudioFocus.Gain:Logger.LogInformation("Gaining audio focus.");mediaPlayer.SetVolume(1f,1f);if(RestartAudioOnGainAudioFocus){Logger.LogInformation("Restarting audio.");_=Play();}else{Logger.LogInformation("Restarting audio not needed.");}break;caseAudioFocus.Loss:Logger.LogInformation("Permanent lost audio focus.");RestartAudioOnGainAudioFocus=false;//We have lost focus stop!awaitStop(true);break;caseAudioFocus.LossTransient:Logger.LogInformation("Transient lost audio focus.");//We have lost focus for a short time// Restart if playingif(this.MediaPlayerState==PlaybackStateCode.Playing){Logger.LogInformation("Was playing. Will restart audio on gain audio focus.");RestartAudioOnGainAudioFocus=true;}else{Logger.LogInformation("Was not playing. Will not restart audio on gain audio focus.");RestartAudioOnGainAudioFocus=false;}awaitPause();break;caseAudioFocus.LossTransientCanDuck://We have lost focus but should till play at a muted 10% volumeif(mediaPlayer.IsPlaying){mediaPlayer.SetVolume(.1f,.1f);}break;}}
WifiLock
MediaPlayerService uses a Wifi-lock. The reason for this seems to be:
“Lock the wifi so we can still stream under lock screen”
Assuming the user has access to mobile data, a Wifi-lock is not required. However, to properly support streaming when the device is locked, a foreground services should be used, see next topic.
Services doesn't need to be exported
I see this in the code:
[Service(Exported=true)
I have changed Exported to false, and it works simply fine.
RemoteControlBroadcastReceiver
The RemoteControlBroadcastReceiver could be removed. As I understand it, this is used to handle ACTION_MEDIA_BUTTON intent. But in Android 5, this was replaced with MediaButtonReceiver that is also implemented. See more details here: https://developer.android.com/guide/topics/media-apps/mediabuttons
I have tried without RemoteControlBroadcastReceiver and haven't found any issues.
Foreground service
Lastly, a bug that took me several months to solve :-( In my application, I could start audio and then after about 20 minutes that playback would be blocked. This only happen if my device wasn’t charging or connected to a computer. When I started the device, the audio started to play again. The reason for this was that the device lost connection to the Internet. The WifiLock mentioned earlier didn’t solve this. It looks like the internal media player is buffering about 20 minutes of audio. Just to prove that internet connection was solved I created this little utility:
classConnectionAliveChecker{privateILoggerLogger{get;}publicConnectionAliveChecker(IServiceProviderserviceProvider){Logger=serviceProvider.GetRequiredService<ILogger<ConnectionAliveChecker>>();}publicasyncvoidRunCheck(){// Infinite loop. Check if vecka.nu is accessible every 10 secondwhile(true){try{using(varclient=newHttpClient()){varresponse=awaitclient.GetAsync("https://vecka.nu");if(response.IsSuccessStatusCode){Logger.LogWarning("ConnectionAliveChecker is alive");}else{Logger.LogError("ConnectionAliveChecker is not alive");}}}catch(Exceptionex){Logger.LogError(ex,"ConnectionAliveChecker is not alive");}awaitTask.Delay(10000);}}}
Then I started this when the application was started. About 5-10 minutes after the device was locked and USB was not connected, it was unable to connect to Internet.
To solve this, the MediaPlayerService needs to be a foreground service. Currently it is just a bound service. I have found no documentation about this limitation :-( First step is to add the android.permission.FOREGROUND_SERVICE should be added to AndroidManifest.xml:
For about a year, I have been working on a podcast app myself. Implementing playback support for Android was something I was hesitating to do, and then luckily for me dotnet-podcasts were released that helped me a lot with this. Thank you for this project!
However, I have found a few issues, and solutions (at least I think so, I haven’t I run dotnet-podcasts myself). I’m too lazy to make a PR, so instead I share these here.
Stopping notifications
If the application for some reason crashes, in many cases the player notification remains open. To solve this (or at least reduce the risk for this), I modified
MainApplication
and added several calls toStopNotification
like this:Pause button in notification
The pause button in the notification is visible when the audio is playing. But it should also be visible when the state is buffering or stopped. In
MediaPlayerService.UpdateNotification
, replace:With:
Position and Duration properties in MediaPlayerService
The properties
Position
andDuration
inMediaPlayerService
have valid values depending on state. I modified these to provide a cached value if the underlying media player cannot provide this:_LatestValidDuration
and_LatestValidPosition
should be set to -1 inPrepareAndPlayMediaPlayerAsync
.MediaPlayerService.Seek
The seek method I have modified to this:
MediaPlayerService.PrepareAndPlayMediaPlayerAsync
In
PrepareAndPlayMediaPlayerAsync
I have added:before the call to
SetDataSourceAsync
. If I remember correctly this solved a couple of cases where anIllegalStateException
was thrown. But I have done other changes too, not sure if this is needed.MediaPlayerService.OnAudioFocusChange
The
MediaPlayerService
automatically starts media player when it gains audio focus. This is great – if the service also has automatically paused the audio. Currently, if the audio is manually paused by the user and then the user gets a phone call (or a notification), the audio will be restarted after the phone call is completed. It is very surprising :-). I rewrote the method like this:WifiLock
MediaPlayerService
uses a Wifi-lock. The reason for this seems to be:Assuming the user has access to mobile data, a Wifi-lock is not required. However, to properly support streaming when the device is locked, a foreground services should be used, see next topic.
Services doesn't need to be exported
I see this in the code:
I have changed
Exported
to false, and it works simply fine.RemoteControlBroadcastReceiver
The
RemoteControlBroadcastReceiver
could be removed. As I understand it, this is used to handle ACTION_MEDIA_BUTTON intent. But in Android 5, this was replaced withMediaButtonReceiver
that is also implemented. See more details here: https://developer.android.com/guide/topics/media-apps/mediabuttonsI have tried without
RemoteControlBroadcastReceiver
and haven't found any issues.Foreground service
Lastly, a bug that took me several months to solve :-( In my application, I could start audio and then after about 20 minutes that playback would be blocked. This only happen if my device wasn’t charging or connected to a computer. When I started the device, the audio started to play again. The reason for this was that the device lost connection to the Internet. The
WifiLock
mentioned earlier didn’t solve this. It looks like the internal media player is buffering about 20 minutes of audio. Just to prove that internet connection was solved I created this little utility:Then I started this when the application was started. About 5-10 minutes after the device was locked and USB was not connected, it was unable to connect to Internet.
To solve this, the
MediaPlayerService
needs to be a foreground service. Currently it is just a bound service. I have found no documentation about this limitation :-( First step is to add theandroid.permission.FOREGROUND_SERVICE
should be added to AndroidManifest.xml:The second step, that I’m not sure if it’s required, is to change the
MediaPlayerService
attribute:To:
See this documentation: https://developer.android.com/guide/topics/manifest/service-element
Third step is to change how the notification is published. In
NotificationHelper.StartNotification
addService service
as a parameter and replace:With:
The text was updated successfully, but these errors were encountered: